123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- #-*- coding: utf-8 -*-
-
- import copy
- import types
- import importlib
-
- class GenericFieldType(object):
-
- help_text = 'Generic field type : abstract class for every fieldtype'
-
- ## @brief List fields that will be exposed to the construct_data_method
- _construct_datas_deps = []
-
- ## @param internal False | str : define wheter or not a field is internal
- # @throw NotImplementedError if called from bad class
- def __init__(self, internal = False, **args):
- if self.__class__ == GenericFieldType:
- raise NotImplementedError("Abstract class")
- self.internal = internal #Check this value ?
-
- for argname, argval in args.items():
- setattr(self, argname, argval)
-
- ## Fieldtype name
- # @todo should be a staticmethod
- @property
- def name(self):
- return self.__module__.split('.')[-1]
-
- ## @return True if a fieldtype is internal
- def is_internal(self):
- return self.internal != False
-
- ## @brief Take care to call the fieldtype defined _check_data_value() method
- # @return a tuple (value, error|None)
- def check_data_value(self, value):
- return self._check_data_value(value)
-
- def _check_data_value(self, value):
- return (value, None)
-
- ## @brief Build field value
- # @param lec LeCrud : A LeCrud child class
- # @param fname str : The field name
- # @param datas dict : dict storing fields values (from the lec)
- # @param cur_value : the value for the current field (identified by fieldname)
- # @return constructed datas (for the fname field)
- # @throw RuntimeError if unable to construct data
- def construct_data(self, lec, fname, datas, cur_value):
- if fname in datas.keys():
- return cur_value
- elif hasattr(lec.fieldtypes()[fname], 'default'):
- return lec.fieldtypes()[fname].default
- elif lec.fieldtypes()[fname].nullable:
- return None
- raise RuntimeError("Unable to construct data for field %s", fname)
-
- ## @brief Check datas consistency
- # @param leo LeCrud : A LeCrud child class instance
- # @param fname str : The field name
- # @param datas dict : dict storing fields values
- # @return an Exception instance if fails else True
- def check_data_consistency(self, lec, fname, datas):
- return True
-
- ## @brief Given a fieldtype name return the associated python class
- # @param fieldtype_name str : A fieldtype name
- # @return An GenericFieldType derivated class
- @staticmethod
- def from_name(fieldtype_name):
- mod = importlib.import_module(GenericFieldType.module_name(fieldtype_name))
- return mod.EmFieldType
-
- ## @brief Get a module name given a fieldtype name
- # @param fieldtype_name str : A fieldtype name
- # @return a string representing a python module name
- @staticmethod
- def module_name(fieldtype_name):
- return 'EditorialModel.fieldtypes.%s' % (fieldtype_name)
-
- ## @brief __hash__ implementation for fieldtypes
- def __hash__(self):
- hash_dats = [self.__class__.__module__]
- for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
- hash_dats.append((kdic, getattr(self, kdic)))
- return hash(tuple(hash_dats))
-
- ## @brief Represent fieldtypes handling a single value
- class SingleValueFieldType(GenericFieldType):
-
- ## @brief Instanciate a new fieldtype
- # @param nullable bool : is None allowed as value ?
- # @param uniqu bool : Indicate if a field should handle uniq value
- # @param primary bool : If True the field is a primary key
- # @param **args : Other arguments
- # @throw NotImplementedError if called from bad class
- def __init__(self, internal = False, nullable = True, uniq = False, primary = False, **args):
- if self.__class__ == SingleValueFieldType:
- raise NotImplementedError("Asbtract class")
-
- super().__init__(internal, **args)
-
- self.nullable = nullable
- self.uniq = uniq
- self.primary = primary
- if 'default' in args:
- self.default, error = self.check_data_value(args['default'])
- if error:
- raise error
- del(args['default'])
-
- def check_data_value(self, value):
- if value is None:
- if not self.nullable:
- return (None, TypeError("'None' value but field is not nullable"))
- return (None, None)
- return super().check_data_value(value)
-
- ## @brief Abstract class for fieldtype representing references
- #
- # In MySQL its foreign keys (for example leo fieldtypes)
- class ReferenceFieldType(SingleValueFieldType):
-
- ## @brief Instanciate a new fieldtype
- #
- #
- # @param reference str : A string that defines the reference (can be 'object' or 'relation')
- # @param nullable bool : is None allowed as value ?
- # @param unique bool : Indicate if a field should handle uniq value
- # @param primary bool : If True the field is a primary key
- # @param **args : Other arguments
- # @throw NotImplementedError if called from bad class
- def __init__(self, reference, internal=False, nullable = True, uniq = False, primary = False, **args):
- if reference.lower() not in ['relation', 'object']:
- raise ValueError("Bad value for reference : %s. Excepted on of 'relation', 'object'" % reference)
- self.reference = reference.lower()
- super().__init__(
- internal = internal,
- nullable = nullable,
- uniq = uniq,
- primary = primary,
- **args
- );
- pass
-
- ## @brief Abstract class for fieldtypes handling multiple values identified by a key
- #
- # For example i18n fieldtype
- class MultiValueFieldType(GenericFieldType):
-
- ## @brief Instanciate a new multivalue fieldtype
- #
- # This fieldtype describe a field that handles multiple values referenced by a key (like a dict).
- # A typicall example is the i18n fieldtype
- # @param keyname str : The identifier key name
- # @param key_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
- # @param value_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
- def __init__(self, keyname, key_fieldtype, value_fieldtype, internal = False, **args):
- super().__init__(internal)
- ## stores the keyname
- self.keyname = keyname
- ## stores the fieldtype that handles the key
- self.key_fieldtype = key_fieldtype
- ## stores the fieldtype that handles the values
- self.value_fieldtype = value_fieldtype
-
- ## @brief Class designed to handle datas access will fieldtypes are constructing datas
- #
- # This class is designed to allow automatic scheduling of construct_data calls.
- #
- # In theory it's able to detect circular dependencies
- # @todo test circular deps detection
- # @todo test circulat deps false positiv
- class DatasConstructor(object):
-
- ## @brief Init a DatasConstructor
- # @param lec LeCrud : LeCrud child class
- # @param datas dict : dict with field name as key and field values as value
- # @param fieldtypes dict : dict with field name as key and fieldtype as value
- def __init__(self, lec, datas, fieldtypes):
- ## Stores concerned class
- self._lec = lec
- ## Stores datas and constructed datas
- self._datas = copy.copy(datas)
- ## Stores fieldtypes
- self._fieldtypes = fieldtypes
- ## Stores list of fieldname for constructed datas
- self._constructed = []
- ## Stores construct calls list
- self._construct_calls = []
-
- ## @brief Implements the dict.keys() method on instance
- def keys(self):
- return self._datas.keys()
-
- ## @brief Allows to access the instance like a dict
- def __getitem__(self, fname):
- if fname not in self._constructed:
- if fname in self._construct_calls:
- raise RuntimeError('Probably circular dependencies in fieldtypes')
- cur_value = self._datas[fname] if fname in self._datas else None
- self._datas[fname] = self._fieldtypes[fname].construct_data(self._lec, fname, self, cur_value)
- self._constructed.append(fname)
- return self._datas[fname]
-
- ## @brief Allows to set instance values like a dict
- # @warning Should not append in theory
- def __setitem__(self, fname, value):
- self._datas[fname] = value
- warnings.warn("Setting value of an DatasConstructor instance")
-
-
- #
- #
- # Exceptions
- #
- #
- class FieldTypeError(Exception):
- pass
-
- class FieldTypeDataCheckError(FieldTypeError):
-
- ## @brief Instanciate a new data check error
- # @param expt_l list : A list of data check Exception
- def __init__(self, expt_l):
- self._expt_l = expt_l
-
- def __str__(self):
- msg = "Data check errors : "
- for expt in self._expt_l:
- msg += "{expt_name}:{expt_msg}; ".format(expt_name=expt.__class__.__name__, expt_msg=str(expt))
- return msg
-
|