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.

base_classes.py 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. # -*- coding: utf-8 -*-
  2. ## @package lodel.leapi.datahandlers.base_classes Define all base/abstract class for data handlers
  3. #
  4. # Contains custom exceptions too
  5. import copy
  6. import importlib
  7. import inspect
  8. class FieldValidationError(Exception):
  9. pass
  10. ## @brief Base class for all data handlers
  11. class DataHandler(object):
  12. __HANDLERS_MODULES = ('datas_base', 'datas', 'references')
  13. ## @brief Stores the DataHandler childs classes indexed by name
  14. __base_handlers = None
  15. ## @brief Stores custom datahandlers classes indexed by name
  16. # @todo do it ! (like plugins, register handlers... blablabla)
  17. __custom_handlers = dict()
  18. help_text = 'Generic Field Data Handler'
  19. ## @brief List fields that will be exposed to the construct_data_method
  20. _construct_datas_deps = []
  21. ## @brief constructor
  22. # @param internal False | str : define whether or not a field is internal
  23. # @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
  24. # designed globally and immutable
  25. # @param **args
  26. # @throw NotImplementedError if it is instanciated directly
  27. def __init__(self, internal=False, immutable=False, primary_key = False, **args):
  28. if self.__class__ == DataHandler:
  29. raise NotImplementedError("Abstract class")
  30. self.primary_key = primary_key
  31. self.internal = internal # Check this value ?
  32. self.immutable = bool(immutable)
  33. for argname, argval in args.items():
  34. setattr(self, argname, argval)
  35. ## Fieldtype name
  36. @staticmethod
  37. def name(cls):
  38. return cls.__module__.split('.')[-1]
  39. def is_primary_key(self):
  40. return self.primary_key
  41. ## @brief checks if a fieldtype is internal
  42. # @return bool
  43. def is_internal(self):
  44. return self.internal is not False
  45. ## @brief calls the data_field defined _check_data_value() method
  46. # @return tuple (value, error|None)
  47. def check_data_value(self, value):
  48. return self._check_data_value(value)
  49. def _check_data_value(self, value):
  50. return value, None
  51. ## @brief checks if this class can override the given data handler
  52. # @param data_handler DataHandler
  53. # @return bool
  54. def can_override(self, data_handler):
  55. if data_handler.__class__.base_type != self.__class__.base_type:
  56. return False
  57. return True
  58. ## @brief Build field value
  59. # @param emcomponent EmComponent : An EmComponent child class instance
  60. # @param fname str : The field name
  61. # @param datas dict : dict storing fields values (from the component)
  62. # @param cur_value : the value from the current field (identified by fieldname)
  63. # @return the value
  64. # @throw RunTimeError if data construction fails
  65. def construct_data(self, emcomponent, fname, datas, cur_value):
  66. emcomponent_fields = emcomponent.fields()
  67. fname_data_handler = None
  68. if fname in emcomponent_fields:
  69. fname_data_handler = DataHandler.from_name(emcomponent_fields[fname])
  70. if fname in datas.keys():
  71. return cur_value
  72. elif fname_data_handler is not None and hasattr(fname_data_handler, 'default'):
  73. return fname_data_handler.default
  74. elif fname_data_handler is not None and fname_data_handler.nullable:
  75. return None
  76. return RuntimeError("Unable to construct data for field %s", fname)
  77. ## @brief Check datas consistency
  78. # @param emcomponent EmComponent : An EmComponent child class instance
  79. # @param fname : the field name
  80. # @param datas dict : dict storing fields values
  81. # @return an Exception instance if fails else True
  82. # @todo A implémenter
  83. def check_data_consistency(self, emcomponent, fname, datas):
  84. return True
  85. ## @brief This method is use by plugins to register new data handlers
  86. @classmethod
  87. def register_new_handler(cls, name, data_handler):
  88. if not inspect.isclass(data_handler):
  89. raise ValueError("A class was expected but %s given" % type(data_handler))
  90. if not issubclass(data_handler, DataHandler):
  91. raise ValueError("A data handler HAS TO be a child class of DataHandler")
  92. cls.__custom_handlers[name] = data_handler
  93. @classmethod
  94. def load_base_handlers(cls):
  95. if cls.__base_handlers is None:
  96. cls.__base_handlers = dict()
  97. for module_name in cls.__HANDLERS_MODULES:
  98. module = importlib.import_module('lodel.leapi.datahandlers.%s' % module_name)
  99. for name, obj in inspect.getmembers(module):
  100. if inspect.isclass(obj):
  101. print("Data handler found : %s in %s" % (name, module_name))
  102. cls.__base_handlers[name.lower()] = obj
  103. return copy.copy(cls.__base_handlers)
  104. ## @brief given a field type name, returns the associated python class
  105. # @param fieldtype_name str : A field type name (not case sensitive)
  106. # @return DataField child class
  107. # @todo implements custom handlers fetch
  108. # @note To access custom data handlers it can be cool to preffix the handler name by plugin name for example ? (to ensure name unicity)
  109. @classmethod
  110. def from_name(cls, name):
  111. name = name.lower()
  112. if name not in cls.__base_handlers:
  113. raise NameError("No data handlers named '%s'" % (name,))
  114. return cls.__base_handlers[name]
  115. ## @brief Return the module name to import in order to use the datahandler
  116. # @param data_handler_name str : Data handler name
  117. # @return a str
  118. @classmethod
  119. def module_name(cls, name):
  120. name = name.lower()
  121. handler_class = cls.from_name(name)
  122. return '{module_name}.{class_name}'.format(
  123. module_name = handler_class.__module__,
  124. class_name = handler_class.__name__
  125. )
  126. ## @brief __hash__ implementation for fieldtypes
  127. def __hash__(self):
  128. hash_dats = [self.__class__.__module__]
  129. for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
  130. hash_dats.append((kdic, getattr(self, kdic)))
  131. return hash(tuple(hash_dats))
  132. ## @brief Base class for datas data handler (by opposition with references)
  133. class DataField(DataHandler):
  134. ## @brief Instanciates a new fieldtype
  135. # @param nullable bool : is None allowed as value ?
  136. # @param uniq bool : Indicates if a field should handle a uniq value
  137. # @param primary bool : If true the field is a primary key
  138. # @param internal str|False: if False, that field is not internal. Other values cans be "autosql" or "internal"
  139. # @param **kwargs : Other arguments
  140. # @throw NotImplementedError if called from bad class
  141. def __init__(self, internal=False, nullable=True, uniq=False, primary=False, **kwargs):
  142. if self.__class__ == DataField:
  143. raise NotImplementedError("Abstract class")
  144. super().__init__(internal, **kwargs)
  145. self.nullable = nullable
  146. self.uniq = uniq
  147. self.primary = primary
  148. if 'defaults' in kwargs:
  149. self.default, error = self.check_data_value(kwargs['default'])
  150. if error:
  151. raise error
  152. del(args['default'])
  153. def check_data_value(self, value):
  154. if value is None:
  155. if not self.nullable:
  156. return None, TypeError("'None' value but field is not nullable")
  157. return None, None
  158. return super().check_data_value(value)
  159. ## @brief Abstract class for all references
  160. #
  161. # References are fields that stores a reference to another
  162. # editorial object
  163. class Reference(DataHandler):
  164. ## @brief Instanciation
  165. # @param allowed_classes list | None : list of allowed em classes if None no restriction
  166. # @param internal bool : if False, the field is not internal
  167. # @param **kwargs : other arguments
  168. def __init__(self, allowed_classes = None, internal=False, **kwargs):
  169. self.__allowed_classes = None if allowed_classes is None else set(allowed_classes)
  170. super().__init__(internal=internal, **kwargs)
  171. ## @brief Check value
  172. # @param value *
  173. # @return tuple(value, exception)
  174. # @todo implement the check when we have LeObject to check value
  175. def _check_data_value(self, value):
  176. return value, None
  177. if isinstance(value, lodel.editorial_model.components.EmClass):
  178. value = [value]
  179. for elt in value:
  180. if not issubclass(elt.__class__, EmClass):
  181. return None, FieldValidationError("Some elements of this references are not EmClass instances")
  182. if self.__allowed_classes is not None:
  183. if not isinstance(elt, self.__allowed_classes):
  184. return None, FieldValidationError("Some element of this references are not valids (don't fit with allowed_classes")
  185. return value
  186. ## @brief This class represent a data_handler for single reference to another object
  187. #
  188. # The fields using this data handlers are like "foreign key" on another object
  189. class SingleRef(Reference):
  190. def __init__(self, allowed_classes = None, **kwargs):
  191. super().__init__(allowed_classes = allowed_classes)
  192. def _check_data_value(self, value):
  193. val, expt = super()._check_data_value(value)
  194. if not isinstance(expt, Exception):
  195. if len(val) > 1:
  196. return None, FieldValidationError("Only single values are allowed for SingleRef fields")
  197. return val, expt
  198. ## @brief This class represent a data_handler for multiple references to another object
  199. #
  200. # The fields using this data handlers are like SingleRef but can store multiple references in one field
  201. # @note SQL implementation could be tricky
  202. class MultipleRef(Reference):
  203. def __init__(self, allowed_classes = None, **kwargs):
  204. super().__init__(allowed_classes = allowed_classes)