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.

generic.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #-*- coding: utf-8 -*-
  2. import copy
  3. import types
  4. import importlib
  5. class GenericFieldType(object):
  6. help_text = 'Generic field type : abstract class for every fieldtype'
  7. ## @brief List fields that will be exposed to the construct_data_method
  8. _construct_datas_deps = []
  9. ## @param internal False | str : define wheter or not a field is internal
  10. # @throw NotImplementedError if called from bad class
  11. def __init__(self, internal = False, **args):
  12. if self.__class__ == GenericFieldType:
  13. raise NotImplementedError("Abstract class")
  14. self.internal = internal #Check this value ?
  15. for argname, argval in args.items():
  16. setattr(self, argname, argval)
  17. ## Fieldtype name
  18. # @todo should be a staticmethod
  19. @property
  20. def name(self):
  21. return self.__module__.split('.')[-1]
  22. ## @return True if a fieldtype is internal
  23. def is_internal(self):
  24. return self.internal != False
  25. ## @brief Take care to call the fieldtype defined _check_data_value() method
  26. # @return a tuple (value, error|None)
  27. def check_data_value(self, value):
  28. return self._check_data_value(value)
  29. def _check_data_value(self, value):
  30. return (value, None)
  31. ## @brief Build field value
  32. # @param lec LeCrud : A LeCrud child class
  33. # @param fname str : The field name
  34. # @param datas dict : dict storing fields values (from the lec)
  35. # @param cur_value : the value for the current field (identified by fieldname)
  36. # @return constructed datas (for the fname field)
  37. # @throw RuntimeError if unable to construct data
  38. def construct_data(self, lec, fname, datas, cur_value):
  39. if fname in datas.keys():
  40. return cur_value
  41. elif hasattr(lec.fieldtypes()[fname], 'default'):
  42. return lec.fieldtypes()[fname].default
  43. elif lec.fieldtypes()[fname].nullable:
  44. return None
  45. raise RuntimeError("Unable to construct data for field %s", fname)
  46. ## @brief Check datas consistency
  47. # @param leo LeCrud : A LeCrud child class instance
  48. # @param fname str : The field name
  49. # @param datas dict : dict storing fields values
  50. # @return an Exception instance if fails else True
  51. def check_data_consistency(self, lec, fname, datas):
  52. return True
  53. ## @brief Given a fieldtype name return the associated python class
  54. # @param fieldtype_name str : A fieldtype name
  55. # @return An GenericFieldType derivated class
  56. @staticmethod
  57. def from_name(fieldtype_name):
  58. mod = importlib.import_module(GenericFieldType.module_name(fieldtype_name))
  59. return mod.EmFieldType
  60. ## @brief Get a module name given a fieldtype name
  61. # @param fieldtype_name str : A fieldtype name
  62. # @return a string representing a python module name
  63. @staticmethod
  64. def module_name(fieldtype_name):
  65. return 'EditorialModel.fieldtypes.%s' % (fieldtype_name)
  66. ## @brief __hash__ implementation for fieldtypes
  67. def __hash__(self):
  68. hash_dats = [self.__class__.__module__]
  69. for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
  70. hash_dats.append((kdic, getattr(self, kdic)))
  71. return hash(tuple(hash_dats))
  72. ## @brief Represent fieldtypes handling a single value
  73. class SingleValueFieldType(GenericFieldType):
  74. ## @brief Instanciate a new fieldtype
  75. # @param nullable bool : is None allowed as value ?
  76. # @param uniqu bool : Indicate if a field should handle uniq value
  77. # @param primary bool : If True the field is a primary key
  78. # @param **args : Other arguments
  79. # @throw NotImplementedError if called from bad class
  80. def __init__(self, internal = False, nullable = True, uniq = False, primary = False, **args):
  81. if self.__class__ == SingleValueFieldType:
  82. raise NotImplementedError("Asbtract class")
  83. super().__init__(internal, **args)
  84. self.nullable = nullable
  85. self.uniq = uniq
  86. self.primary = primary
  87. if 'default' in args:
  88. self.default, error = self.check_data_value(args['default'])
  89. if error:
  90. raise error
  91. del(args['default'])
  92. def check_data_value(self, value):
  93. if value is None:
  94. if not self.nullable:
  95. return (None, TypeError("'None' value but field is not nullable"))
  96. return (None, None)
  97. return super().check_data_value(value)
  98. ## @brief Abstract class for fieldtype representing references
  99. #
  100. # In MySQL its foreign keys (for example leo fieldtypes)
  101. class ReferenceFieldType(SingleValueFieldType):
  102. ## @brief Instanciate a new fieldtype
  103. #
  104. #
  105. # @param reference str : A string that defines the reference (can be 'object' or 'relation')
  106. # @param nullable bool : is None allowed as value ?
  107. # @param unique bool : Indicate if a field should handle uniq value
  108. # @param primary bool : If True the field is a primary key
  109. # @param **args : Other arguments
  110. # @throw NotImplementedError if called from bad class
  111. def __init__(self, reference, internal=False, nullable = True, uniq = False, primary = False, **args):
  112. if reference.lower() not in ['relation', 'object']:
  113. raise ValueError("Bad value for reference : %s. Excepted on of 'relation', 'object'" % reference)
  114. self.reference = reference.lower()
  115. super().__init__(
  116. internal = internal,
  117. nullable = nullable,
  118. uniq = uniq,
  119. primary = primary,
  120. **args
  121. );
  122. pass
  123. ## @brief Abstract class for fieldtypes handling multiple values identified by a key
  124. #
  125. # For example i18n fieldtype
  126. class MultiValueFieldType(GenericFieldType):
  127. ## @brief Instanciate a new multivalue fieldtype
  128. #
  129. # This fieldtype describe a field that handles multiple values referenced by a key (like a dict).
  130. # A typicall example is the i18n fieldtype
  131. # @param keyname str : The identifier key name
  132. # @param key_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
  133. # @param value_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
  134. def __init__(self, keyname, key_fieldtype, value_fieldtype, internal = False, **args):
  135. super().__init__(internal)
  136. ## stores the keyname
  137. self.keyname = keyname
  138. ## stores the fieldtype that handles the key
  139. self.key_fieldtype = key_fieldtype
  140. ## stores the fieldtype that handles the values
  141. self.value_fieldtype = value_fieldtype
  142. ## @brief Class designed to handle datas access will fieldtypes are constructing datas
  143. #
  144. # This class is designed to allow automatic scheduling of construct_data calls.
  145. #
  146. # In theory it's able to detect circular dependencies
  147. # @todo test circular deps detection
  148. # @todo test circulat deps false positiv
  149. class DatasConstructor(object):
  150. ## @brief Init a DatasConstructor
  151. # @param lec LeCrud : LeCrud child class
  152. # @param datas dict : dict with field name as key and field values as value
  153. # @param fieldtypes dict : dict with field name as key and fieldtype as value
  154. def __init__(self, lec, datas, fieldtypes):
  155. ## Stores concerned class
  156. self._lec = lec
  157. ## Stores datas and constructed datas
  158. self._datas = copy.copy(datas)
  159. ## Stores fieldtypes
  160. self._fieldtypes = fieldtypes
  161. ## Stores list of fieldname for constructed datas
  162. self._constructed = []
  163. ## Stores construct calls list
  164. self._construct_calls = []
  165. ## @brief Implements the dict.keys() method on instance
  166. def keys(self):
  167. return self._datas.keys()
  168. ## @brief Allows to access the instance like a dict
  169. def __getitem__(self, fname):
  170. if fname not in self._constructed:
  171. if fname in self._construct_calls:
  172. raise RuntimeError('Probably circular dependencies in fieldtypes')
  173. cur_value = self._datas[fname] if fname in self._datas else None
  174. self._datas[fname] = self._fieldtypes[fname].construct_data(self._lec, fname, self, cur_value)
  175. self._constructed.append(fname)
  176. return self._datas[fname]
  177. ## @brief Allows to set instance values like a dict
  178. # @warning Should not append in theory
  179. def __setitem__(self, fname, value):
  180. self._datas[fname] = value
  181. warnings.warn("Setting value of an DatasConstructor instance")
  182. #
  183. #
  184. # Exceptions
  185. #
  186. #
  187. class FieldTypeError(Exception):
  188. pass
  189. class FieldTypeDataCheckError(FieldTypeError):
  190. ## @brief Instanciate a new data check error
  191. # @param expt_l list : A list of data check Exception
  192. def __init__(self, expt_l):
  193. self._expt_l = expt_l
  194. def __str__(self):
  195. msg = "Data check errors : "
  196. for expt in self._expt_l:
  197. msg += "{expt_name}:{expt_msg}; ".format(expt_name=expt.__class__.__name__, expt_msg=str(expt))
  198. return msg