示例#1
0
文件: funcs.py 项目: Ma233/olo
class Function(
        with_metaclass(FunctionMeta, SQLLiteralInterface,
                       BinaryOperationMixin)):

    from olo.expression import BinaryExpression  # noqa

    def __init__(self, *args):
        self.args = args

    def alias(self, name):
        inst = self.__class__(*self.args)
        inst._alias_name = name
        return inst

    def _get_sql_and_params(self):
        pieces = []
        params = []

        for arg in self.args:
            piece, _params = sql_and_params(arg, coerce=repr)

            pieces.append(piece)
            if _params:
                params.extend(_params)

        s = '{}({})'.format(self.__class__.__name__.upper(), ', '.join(pieces))

        return s, params

    def get_sql_and_params(self):
        s, params = self._get_sql_and_params()

        if self.alias_name:
            return '{} AS {}'.format(s, self.alias_name), params
        return s, params
示例#2
0
文件: funcs.py 项目: tyong920/olo
class Function(
        with_metaclass(
            FunctionMeta, BinaryOperationMixin,
            SQLASTInterface
        )
):

    from olo.expression import BinaryExpression  # noqa

    def __init__(self, *args):
        self.args = args

    def alias(self, name):
        inst = self.__class__(*self.args)
        inst._alias_name = name
        return inst

    def _get_sql_ast(self):
        sql_ast = [
            'CALL',
            self.__class__.__name__.upper()
        ] + [arg.get_sql_ast() if isinstance(arg, SQLASTInterface) else arg
             for arg in self.args]
        return sql_ast

    def get_sql_ast(self):
        sql_ast = self._get_sql_ast()
        if self.alias_name:
            sql_ast = ['ALIAS', sql_ast, self.alias_name]
        return sql_ast
示例#3
0
class Model(with_metaclass(ModelMeta)):

    AES_KEY = '*' * 32

    _olo_is_new = True
    _olo_qs = None
    _olo_qs_idx = 0

    def __init__(self, _olo_is_new=None, _olo_decrypt=True, **attrs):
        depth = 0
        if getattr(self.__class__, '_olo_is_breaked', False):
            depth = self.__ctx__.instantiate_depth or 0
        if not isinstance(_olo_is_new, bool):
            _olo_is_new = depth <= 1
        self._olo_is_new = _olo_is_new
        self._olo_decrypt = _olo_decrypt and not self._olo_is_new

        if self._olo_is_new:
            self._check_attrs(attrs)
            attrs = self._wash_attrs(attrs)
            attrs = self._olo_append_default_attrs(attrs)

        if self.__encrypted_fields__ and self._olo_decrypt:
            attrs = decrypt_attrs(self.__class__, attrs)

        self._init()
        self._data = attrs

    def _init(self):
        self._parsed_data = {}
        self._dirty_fields = set()
        self._orig = None

    def _clone(self):
        return copy(self)

    def _set_orig(self):
        if self._olo_is_new:
            return
        self._orig = self._clone()

    @override
    def get_uuid(self):
        raise NotImplementedError

    def get_finally_uuid(self):
        uuid = self.get_uuid()
        return '{}/props'.format(uuid)

    def __getstate__(self):
        dct = dict(self.__dict__)
        dct.pop('_dirty_fields', None)
        dct.pop('_orig', None)
        dct.pop('_parsed_data', None)
        dct = dict(dct)
        _data = dct.get('_data', {})
        if _data:
            dct['_data'] = dict(_data)
        # Return tuple to distinguish the old version
        return (dct, )

    def __setstate__(self, state):
        if isinstance(state, tuple):
            self.__dict__.update(state[0])
        else:
            self._data = state  # pragma: no cover
        self._init()

    @classmethod
    def _olo_instantiate(cls, **attrs):
        _olo_is_new = attrs.pop('_olo_is_new', False)
        return cls._instantiate(_olo_is_new=_olo_is_new, **attrs)

    @classmethod
    def _instantiate(cls, **attrs):
        return cls(**attrs)

    @classmethod
    def _check_choices(cls, attrs):
        for field_name in cls.__choices_field_sets__:
            v = attrs.get(field_name, missing)
            if v is not missing:
                getattr(cls, field_name).validate(v)

    def _check_validates(self, attrs):
        for field_name, validate in self.__validates__.iteritems():
            v = attrs.get(field_name, missing)
            if v is not missing:
                validate(v)
        self.olo_validate()

    def _validate_attrs(self, attrs, parse=True, decrypt=True, output=True):
        if parse:
            parsed_attrs = self._parse_attrs(attrs,
                                             decrypt=decrypt,
                                             output=output)
        else:
            parsed_attrs = attrs
        self._check_choices(parsed_attrs)
        self._check_validates(parsed_attrs)
        return parsed_attrs

    def _clear_cache(self):
        delete_cache(self)

    def _rollback(self):
        if self._orig:
            self._data.update(self._orig._data)
            self._init()

    def is_dirty(self):
        return bool(self._dirty_fields)

    def save(self):
        if self._olo_is_new:
            return self._olo_insert()
        if not self._dirty_fields:
            return False
        attrs = {key: getattr(self, key) for key in self._dirty_fields}
        is_success = self.update(**attrs)
        self._dirty_fields.clear()
        return is_success

    def update(self, **attrs):
        self._check_attrs(attrs)

        attrs = self._wash_attrs(attrs)

        if not attrs:
            return False

        if self._orig is None:
            self._set_orig()

        if self.before_update(**attrs) is False:
            self._rollback()
            return False

        db = self._get_db()

        need_updates = {}
        for k, v in self.__on_updates__.iteritems():
            if k in attrs:
                continue

            try:
                res = v()
            except TypeError:
                res = v(self)

            need_updates[k] = res

        attrs = dict(need_updates, **attrs)
        expressions, sql_attrs, db_attrs = self._split_attrs(attrs)

        sql_attrs = self._validate_attrs(sql_attrs, decrypt=False)
        db_attrs = self._validate_attrs(db_attrs, decrypt=False)
        clean_attrs = dict(sql_attrs, **db_attrs)

        for k in db_attrs:
            # cache old db values
            getattr(self._orig, k, None)

        next_inst = self._clone()
        next_inst.__setstate__(dict(self._data, **clean_attrs))
        can_update = self._orig._will_update(
            next_inst,
            fields=clean_attrs.keys(),
        )
        if can_update is False:
            self._rollback()
            return False

        if expressions:
            expression, _params = self.unique_expression_and_params
            if not _params:
                raise ExpressionError(
                    'Cannot update this instance because of '  # noqa pragma: no cover
                    'the model has no primary_key '
                    'and unique_key')

            sql_pieces, params = get_sql_pieces_and_params(expressions)
            if _params:
                params.extend(_params)

            sql = ('UPDATE `{table_name}` SET {columns} '
                   'WHERE {expression} ').format(
                       table_name=self._get_table_name(),
                       columns=', '.join(sql_pieces),
                       expression=expression)

            try:
                db.execute(sql, params)
                if db.autocommit:
                    db.commit()
            except Exception:
                if db.autocommit:
                    db.rollback()  # pragma: no cover
                raise

            dynamic_exps = [
                exp for exp in expressions
                if isinstance(exp.right, Expression)
            ]
            if dynamic_exps:
                keys = map(lambda x: x.left.attr_name, dynamic_exps)
                q = self.__class__.query(*keys).filter(
                    **{
                        attr_name: getattr(self, attr_name)
                        for attr_name in self.__primary_key__
                    })
                values = q.first()
                if not isinstance(values, tuple):
                    values = [values]
                _attrs = dict(izip(keys, values))
                sql_attrs.update(self._parse_attrs(_attrs))

        before_update.send(self)

        clean_attrs = dict(sql_attrs, **db_attrs)
        self._data.update(clean_attrs)
        for k in clean_attrs:
            self._parsed_data.pop(k, None)

        for k, v in db_attrs.iteritems():
            field = getattr(self.__class__, k)
            field.db_set(self, v)

        _orig = self._orig

        def func():
            db.commit_beansdb()
            after_update.send(self)
            self.after_update()
            if _orig is not None:
                self._orig = None
                self._did_update(_orig,
                                 fields=chain.from_iterable([
                                     sql_attrs.iterkeys(),
                                     db_attrs.iterkeys(),
                                 ]))

        def rollback_handler():
            self._rollback()

        if db.autocommit:
            func()
        else:
            db.add_lazy_func(func)
            db.add_rollback_handler(rollback_handler)

        return True

    def delete(self, **kwargs):
        if self.before_delete(**kwargs) is False:
            return
        expression, params = self.unique_expression_and_params
        if not params:
            raise ExpressionError(
                'Cannot delete this instance because of '  # noqa pragma: no cover
                'the model has no primary_key '
                'and unique_key')

        sql = ('DELETE FROM `{table_name}` '
               'WHERE {expression} ').format(table_name=self._get_table_name(),
                                             expression=expression)

        db = self._get_db()
        db.execute(sql, params)

        def func():
            after_delete.send(self)
            self.after_delete(**kwargs)

        if db.autocommit:
            db.commit()
            func()
        else:
            db.add_lazy_func(func)

        return True

    @override
    def to_json(self):
        return self.to_dict(jsonize=True)

    @override
    def to_dict(self,
                excludes=None,
                parsers=None,
                type_parsers=None,
                jsonize=False):
        excludes = excludes or []
        parsers = parsers or {}
        if type_parsers is None:
            type_parsers = {}
        if jsonize:
            type_parsers.update({
                datetime: _datetime_parser,
                date: _date_parser,
            })

        res = {}

        for k in chain(self.__all_fields__, self.__exported_property_names__):
            if k in excludes:
                continue
            v = getattr(self, k)
            if isinstance(v, Field):
                continue  # pragma: no cover
            parser = parsers.get(k, _default_parser)
            parser = type_parsers.get(type(v), parser)
            res[k] = parser(v)

        return res

    @classmethod
    def _olo_get_field(cls, attr_name):
        if attr_name not in cls.__fields__:
            return
        return getattr(cls, attr_name)

    @classmethod
    def _olo_get_db_field(cls, attr_name):
        if attr_name not in cls.__db_fields__:
            return
        return getattr(cls, attr_name)

    @classmethod
    def _split_attrs(cls, attrs, collect_expression=True):
        expressions = []
        sql_attrs = {}
        db_attrs = {}
        for k, v in attrs.iteritems():
            if k in cls.__db_fields__:
                db_attrs[k] = v
            elif k in cls.__fields__:
                if not isinstance(v, Expression):
                    sql_attrs[k] = v
                if collect_expression:
                    f = getattr(cls, k)
                    v = cls._deparse_attrs({k: v})[k]
                    expressions.append(BinaryExpression(f, v, '='))
        return expressions, sql_attrs, db_attrs

    @classmethod
    def _check_attrs(cls, attrs):
        key = '_olo_dir_cache'

        cls_attrs = cls.__dict__.get(key)
        if cls_attrs is None:
            cls_attrs = set(dir(cls))
            setattr(cls, key, cls_attrs)

        invalid_attrs = set(attrs) - cls_attrs

        if invalid_attrs:
            raise InvalidFieldError(
                'Cannot found the attributes from {}: {}'.format(
                    cls.__name__, ', '.join(invalid_attrs)))

    @classmethod
    def _wash_attrs(cls, attrs):
        return {k: v for k, v in attrs.iteritems() if v is not missing}

    @classmethod
    def _map_attrs(cls, attrs):
        return {  # pragma: no cover
            getattr(cls, k).name: v
            for k, v in attrs.iteritems()
        }

    @classmethod
    def create(cls, **attrs):
        inst = cls._olo_instantiate(_olo_is_new=True, **attrs)
        if inst._olo_insert():
            return inst

    def _olo_insert(self):
        if not self._olo_is_new:
            return False  # pragma: no cover

        before_create_is_instance_method = getattr(self.before_create,
                                                   '__self__', None) is self  # noqa pylint: disable=C

        if before_create_is_instance_method:
            bcr = self.before_create()

        attrs = dict(self._data)
        _, sql_attrs, db_attrs = self._split_attrs(attrs)

        if not before_create_is_instance_method:
            bcr = self.before_create(**attrs)  # pragma: no cover

        if bcr is False:
            return False

        self._validate_attrs(attrs, parse=True, decrypt=self._olo_decrypt)

        db = self._get_db()

        expressions, _, _ = self._split_attrs(sql_attrs)

        if expressions:
            sql_pieces = []
            params = []

            for exp in expressions:
                piece, _ = sql_and_params(exp.left)
                _, _params = sql_and_params(exp)
                sql_pieces.append(piece)
                if _params:
                    params.extend(_params)

            sql = ('INSERT INTO `{table_name}`({columns}) '
                   'VALUES({values}) ').format(
                       table_name=self._get_table_name(),
                       columns=', '.join(sql_pieces),
                       values=', '.join(['%s'] * len(params)))

            try:
                id_ = db.execute(sql, params)
                if db.autocommit:
                    db.commit()
            except Exception:
                if db.autocommit:
                    db.rollback()  # pragma: no cover
                raise

            pk_name = self.get_singleness_pk_name()

            if (hasattr(self.__class__, pk_name)
                    and pk_name in self.__class__.__fields__
                    and pk_name not in self._data):
                self._data[pk_name] = id_

            # need thinking
            self._extend_missing_data()

        for k, v in db_attrs.iteritems():
            field = getattr(self.__class__, k)
            field.db_set(self, v)

        self._olo_is_new = False

        def rollback_handler():
            self._olo_is_new = True

        def func():
            db.commit_beansdb()
            after_insert.send(self)
            if getattr(self.after_create, '__self__', None) is self:
                self.after_create()
            else:
                self.after_create(self)  # pragma: no cover pylint: disable=E

        if db.autocommit:
            func()
        else:
            db.add_lazy_func(func)
            db.add_rollback_handler(rollback_handler)

        return True

    @classmethod
    def get_singleness_pk_attr_name(cls):
        pk = cls.__primary_key__
        pkl = len(pk)
        if pkl != 1:
            raise ExpressionError(
                'This method only support singleness primary key now. '
                'But your primary key has {} keys'.format(pkl))
        return list(pk)[0]

    @classmethod
    def get_singleness_pk_field(cls):
        attr_name = cls.get_singleness_pk_attr_name()
        return getattr(cls, attr_name)

    @classmethod
    def get_singleness_pk_name(cls, default='id'):
        try:
            field = cls.get_singleness_pk_field()
        except ExpressionError:
            return default
        return field.name

    def _get_singleness_pk_value(self):
        field = self.get_singleness_pk_field()
        return getattr(self, field.attr_name)

    def _olo_get_pk_value(self):
        pk = self.__primary_key__
        return tuple(getattr(self, attr_name) for attr_name in pk)

    def _olo_get_signature(self):
        pk_value = self._olo_get_pk_value()
        return (self.__table_name__, ) + pk_value

    def _extend_missing_data(self):
        missing_fields = [
            getattr(self.__class__, k) for k in self.__fields__
            if k not in self._data
        ]
        if not missing_fields:
            return  # pragma: no cover
        pk_dict = self._get_pk_dict()
        if not pk_dict:
            raise Exception('No pk dict!!!')  # pragma: no cover
        values = self.__class__.query(*missing_fields).filter(
            **pk_dict).first()
        if len(missing_fields) == 1 and values is not None and not isinstance(
                values, list):
            values = [values]
        if values:
            self._data.update(
                dict(izip(map(lambda f: f.attr_name, missing_fields), values)))

    def _get_pk_dict(self):
        dct = {}
        for attr_name in self.__primary_key__:
            v = getattr(self, attr_name, missing)
            if v is not missing:
                dct[attr_name] = v
        return dct

    @classmethod
    def _get(cls, id=None, **kwargs):
        if not kwargs:
            pk_name = cls.get_singleness_pk_name()
            return cls._get_by(**{pk_name: id})
        return cls._get_by(**kwargs)

    @classmethod
    def get(cls, id=None, **kwargs):
        opt = cls._options
        if opt.cache_client and opt.auto_use_cache:
            return cls.cache.get(id=id, **kwargs)
        return cls._get(id=id, **kwargs)

    @classmethod
    def _get_multi(cls, idents, filter_none=True):
        if not idents:
            return []
        if not type_checker([dict], idents):
            pk_name = cls.get_singleness_pk_name()
            pk_field = getattr(cls, pk_name)
            items = cls._get_multi_by(pk_field.in_(idents))
            mapping = {str(getattr(item, pk_name)): item for item in items}
        else:
            ident = idents[0]
            items = cls._get_multi_by(
                UnionField(*[getattr(cls, k) for k in ident]).in_(
                    [_ident.values() for _ident in idents]))
            mapping = {
                tuple(str(getattr(item, k)) for k in ident): item
                for item in items
            }
        res = []
        for ident in idents:
            if not isinstance(ident, dict):
                item = mapping.get(str(ident))
            else:
                item = mapping.get(tuple(map(str, ident.values())))
            if item is None and filter_none:
                continue
            res.append(item)
        return res

    @classmethod
    def get_multi(cls, idents, filter_none=True):
        opt = cls._options
        if opt.cache_client and opt.auto_use_cache:
            return cls.cache.get_multi(idents, filter_none=filter_none)
        return cls._get_multi(idents, filter_none=filter_none)

    @classmethod
    def gets(cls, idents, filter_none=True):
        deprecation('The class method Model.gets is deprecated, '
                    'please use Model.get_multi!')
        return cls.get_multi(idents, filter_none=filter_none)

    @classmethod
    def _get_by(cls, *expressions, **expression_dict):
        return cls.query.filter(*expressions, **expression_dict).one()

    @classmethod
    def get_by(cls, *expressions, **expression_dict):
        opt = cls._options
        if opt.cache_client and opt.auto_use_cache:
            return cls.cache.get_by(*expressions, **expression_dict)
        return cls._get_by(*expressions, **expression_dict)

    @classmethod
    def get_multi_by_random(cls, *expressions, **expression_dict):
        expression_dict.update({'order_by': RAND()})  # pragma: no cover
        return cls.gets_by(*expressions, **expression_dict)  # pragma: no cover

    @classmethod
    def gets_by_random(cls, *expressions, **expression_dict):
        deprecation(  # pragma: no cover
            'The class method Model.gets_by_random is deprecated, '
            'please use Model.get_multi_by_random!')
        return cls.get_multi_by_random(
            *expressions, **expression_dict)  # noqa pragma: no cover

    @classmethod
    def _get_multi_by(cls, *expressions, **expression_dict):
        return cls._get_multi_by_with_query(cls.query, *expressions,
                                            **expression_dict)

    @classmethod
    def get_multi_by(cls, *expressions, **expression_dict):
        opt = cls._options
        if opt.cache_client and opt.auto_use_cache:
            return cls.cache.get_multi_by(*expressions, **expression_dict)
        return cls._get_multi_by(*expressions, **expression_dict)

    @classmethod
    def gets_by(cls, *expressions, **expression_dict):
        deprecation('The class method Model.gets_by is deprecated, '
                    'please use Model.get_multi_by!')
        return cls.get_multi_by(*expressions, **expression_dict)

    @classmethod
    def get_entities_by(cls, entities, *expressions, **expression_dict):
        return cls._get_multi_by_with_query(cls.query(*entities), *expressions,
                                            **expression_dict)

    @classmethod
    def _get_multi_by_with_query(cls, query, *expressions, **expression_dict):
        start = expression_dict.pop('start', None)
        limit = expression_dict.pop('limit', None)
        order_by = expression_dict.pop('order_by', None)
        group_by = expression_dict.pop('group_by', None)

        q = query
        if start is not None:
            q = q.offset(start)
        if limit is not None:
            q = q.limit(limit)
        if order_by is not None:
            if not isinstance(order_by, (list, tuple)):
                order_by = [order_by]
            q = q.order_by(*order_by)
        if group_by is not None:
            if not isinstance(group_by, (list, tuple)):
                group_by = [group_by]
            q = q.group_by(*group_by)

        return q.filter(*expressions, **expression_dict).all()

    @classmethod
    def _count_by(cls, *expressions, **expression_dict):
        return cls.query.filter(*expressions, **expression_dict).count()

    @classmethod
    def count_by(cls, *expressions, **expression_dict):
        opt = cls._options
        if opt.cache_client and opt.auto_use_cache:
            return cls.cache.count_by(*expressions, **expression_dict)
        return cls._count_by(*expressions, **expression_dict)

    @classmethod
    def _get_table_name(cls):
        return cls.__table_name__

    @classmethod
    def _get_db(cls):
        return cls._options.db

    @classmethod
    def _get_columns_str(cls):
        table_name = cls._get_table_name()
        attr_name = '_{}_{}_columns_str'.format(table_name,
                                                bool(context.field_verbose))
        if attr_name not in cls.__dict__:
            sql_pieces, _ = get_sql_pieces_and_params(
                map(lambda x: getattr(cls, x), cls.__fields__))
            setattr(cls, attr_name, ', '.join(sql_pieces))
        return getattr(cls, attr_name)

    @classmethod
    def get_sql_and_params(cls):
        return cls._get_columns_str(), []

    @classmethod
    def _parse_attrs(cls, attrs, decrypt=True, output=True):
        return parse_attrs(cls, attrs, decrypt=decrypt, output=output)

    @classmethod
    def _olo_append_default_attrs(cls, attrs, fields=None):
        if fields is None:
            fields = cls.__all_fields__

        res = {}
        for k in fields:
            field = getattr(cls, k)
            if k in attrs:
                v = attrs[k]
                if (v is None and not field.noneable):
                    v = field.get_default()
            elif field.default is not None:
                v = attrs[k] = field.get_default()
            else:
                continue  # pragma: no cover
            res[k] = v
        return res

    @classmethod
    def _deparse_attrs(cls, attrs):
        res = {}
        for k, v in attrs.iteritems():
            field = getattr(cls, k)
            is_field = isinstance(field, Field)
            if not isinstance(v, Expression):
                if v is not None or not field.noneable:
                    if is_field and not isinstance(v, VALID_TYPES):
                        v = field.deparse(v)
                    if is_field and not isinstance(v, VALID_TYPES):
                        raise DeparseError(  # pragma: no cover
                            'The deparsed type of {}.{} is invalid. '
                            'Type: {}; Value: {}. '
                            'Please check the deparser of this field.'.format(
                                cls.__name__, k, type(v), repr(v)))
                    v = field.encrypt_func(v) if field.encrypt else v
                    v = field.input(v) if field.input else v
            res[k] = v
        return res

    @classmethod
    def _gen_cache_key(cls, _olo_suffix='_olo_data', **kwargs):
        suffix = _olo_suffix
        old_kwargs = dict(kwargs)
        _kwargs = dict(kwargs)
        _kwargs.pop('order_by', None)
        kwargs = cls._parse_attrs(_kwargs)
        old_kwargs.update(kwargs)
        key = '{}:db:{}:({}):{}'.format(
            cls._options.cache_key_prefix, cls._get_table_name(),
            ','.join('{}={}'.format(k, repr(v))
                     for k, v in sorted(old_kwargs.iteritems())),
            cls._options.cache_key_version)
        if suffix:
            key += ':suffix:%s' % suffix
        return key.replace(' ', '&nbsp;')

    @property
    def unique_expression_and_params(self):
        keys = []

        if self.__primary_key__:
            keys.extend(self.__primary_key__)
        elif self.__unique_keys__:  # pragma: no cover
            keys = self.__unique_keys__[0]  # pragma: no cover

        expression = None
        for attr_name in keys:
            _expression = (getattr(self.__class__,
                                   attr_name) == getattr(self, attr_name))
            if expression is None:
                expression = _expression
            else:
                expression &= _expression  # pragma: no cover

        if expression is None:
            return '', []  # pragma: no cover

        return sql_and_params(expression)

    def __repr__(self):
        class_name = self.__class__.__name__
        return '{class_name}({kwargs})'.format(
            class_name=class_name,
            kwargs=', '.join('{}={}'.format(k, friendly_repr(getattr(self, k)))
                             for k in self.__ordered_field_attr_names__))

    __str__ = __repr__

    @override
    def will_update(self, next_inst):
        return True

    def _will_update(self, next_inst, fields=None):
        can_update = self.will_update(next_inst)
        if can_update is False:
            return False

        for k in fields or self._dirty_fields:
            method_name = '{}_will_update'.format(k)
            method = getattr(self, method_name, None)
            if not callable(method):
                continue
            v = getattr(self, k, None)
            next_v = getattr(next_inst, k, None)
            if v != next_v:
                # pylint: disable=E1102
                can_update = method(next_v)
                # pylint: enable=E1102
                if can_update is False:
                    return False
        return True

    @override
    def did_update(self, pre_inst):
        pass

    def _did_update(self, pre_inst, fields=None):
        self.did_update(pre_inst)
        for k in fields or self._dirty_fields:
            method_name = '{}_did_update'.format(k)
            method = getattr(self, method_name, None)
            if not callable(method):
                continue
            v = getattr(self, k, None)
            pre_v = getattr(pre_inst, k, None)
            if v != pre_v:
                # pylint: disable=E1102
                method(pre_v)
                # pylint: enable=E1102

    @override
    def after_update(self):
        pass

    @override
    def before_update(self, **attrs):
        pass

    @override
    def before_create(self):
        pass

    @override
    def after_create(self):
        pass

    @override
    def before_delete(self, **kwargs):
        pass

    @override
    def after_delete(self, **kwargs):
        pass

    @override
    def olo_validate(self):
        pass