# -*- coding: utf-8 -*- from __future__ import absolute_import from peewee import BaseQuery from peewee import SelectQuery from peewee import ForeignKeyAccessor from peewee import BackrefAccessor from peewee import ManyToManyQuery from peewee import BaseModelSelect from peewee import database_required from nplusone.core import signals def parse_get_object(args, kwargs, context): accessor, instance = args return accessor.field.model, to_key(instance), accessor.field.name def parse_reverse_get(args, kwargs, context): accessor, instance = args return accessor.field.rel_model, to_key(instance), accessor.field.backref def get_rel_instance(self, instance): value = instance.__data__.get(self.name) if value is not None or self.name in instance.__rel__: if self.name not in instance.__rel__: signals.lazy_load.send( signals.get_worker(), args=(self, instance), parser=parse_get_object, ) obj = self.rel_model.get(self.field.rel_field == value) instance.__rel__[self.name] = obj return instance.__rel__[self.name] elif not self.field.null: raise self.rel_model.DoesNotExist return value ForeignKeyAccessor.get_rel_instance = get_rel_instance def backref_get(self, instance, instance_type=None): if instance is not None: dest = self.field.rel_field.name query = (self.rel_model .select() .where(self.field == getattr(instance, dest))) # Mark query with context so that we can emit a `lazyload` signal # if evaluated during `BaseQuery.execute` query._context = { 'args': (self, instance), 'kwargs': {'instance_type', instance_type}, } return query return self # pragma: no cover; pasted from peewee BackrefAccessor.__get__ = backref_get def to_key(instance): model = type(instance) return ':'.join([model.__name__, format(instance.get_id())]) def parse_load(args, kwargs, context, ret): return [to_key(row) for row in ret] def is_single(offset, limit): return limit is not None and limit - (offset or 0) == 1 original_model_select_iter = BaseModelSelect.__iter__ def model_select_iter(self): if isinstance(self, ManyToManyQuery): signals.lazy_load.send( signals.get_worker(), args=(self._accessor, self._instance), parser=parse_get_object, ) return original_model_select_iter(self) BaseModelSelect.__iter__ = model_select_iter original_query_execute = BaseQuery.execute def query_execute(self, database): ret = original_query_execute(self, database) if hasattr(self, '_context'): # Query has been marked as lazy during backref lookup signals.lazy_load.send( signals.get_worker(), args=self._context['args'], kwargs=self._context['kwargs'], parser=parse_reverse_get, ) if not isinstance(self, SelectQuery): return ret signal = ( signals.ignore_load if is_single(self._offset, self._limit) else signals.load ) signal.send( signals.get_worker(), args=(self, ), ret=list(ret), parser=parse_load, ) return ret BaseQuery.execute = database_required(query_execute)