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
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
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(' ', ' ') @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