Ticket #17: 5737_instance_caching.diff

File 5737_instance_caching.diff, 8.6 KB (added by Brian Rosner <brosner@…>, 8 years ago)

updated patch to #5737 with some minor edits

  • django/db/models/base.py

     
    1515from django.utils.encoding import smart_str, force_unicode
    1616from django.conf import settings
    1717from itertools import izip
     18from weakref import WeakValueDictionary
    1819import types
    1920import sys
    2021import os
     
    7677        # should only be one class for each model, so we must always return the
    7778        # registered version.
    7879        return get_model(new_class._meta.app_label, name, False)
     80   
     81    def __call__(cls, *args, **kwargs):
     82        if not kwargs.pop("disable_instance_cache", False) \
     83            and cls._meta.has_auto_field:
     84            key = cls._get_cache_key(args, kwargs)
     85            if key is not None:
     86                obj = cls.__instance_cache__.get(key)
     87                if obj is None:
     88                    obj = super(ModelBase, cls).__call__(*args, **kwargs)
     89                    cls.__instance_cache__[key] = obj
     90            else:
     91                obj = super(ModelBase, cls).__call__(*args, **kwargs)
     92        else:
     93            obj = super(ModelBase, cls).__call__(*args, **kwargs)
     94        return obj
    7995
    8096class Model(object):
    8197    __metaclass__ = ModelBase
     
    96112
    97113    def __ne__(self, other):
    98114        return not self.__eq__(other)
     115   
     116    def _get_cache_key(cls, args, kwargs):
     117        # this should be calculated *once*, but isn't atm
     118        pk_index = cls._meta.fields.index(cls._meta.pk)
     119        if len(args) > pk_index:
     120            return args[pk_index]
     121        pk = cls._meta.pk
     122        if pk.name in kwargs:
     123            return kwargs[pk.name]
     124        elif pk.attname in kwargs:
     125            return kwargs[pk.attname]
     126        return None
     127    _get_cache_key = classmethod(_get_cache_key)
     128   
     129    def get_cached_instance(cls, key):
     130        return cls.__instance_cache__.get(key)
     131    get_cached_instance = classmethod(get_cached_instance)
    99132
    100133    def __init__(self, *args, **kwargs):
    101134        dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
     
    196229
    197230        if hasattr(cls, 'get_absolute_url'):
    198231            cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url)
     232       
     233        cls.__instance_cache__ = WeakValueDictionary()
    199234
    200235        dispatcher.send(signal=signals.class_prepared, sender=cls)
    201236
     
    254289            if self._meta.has_auto_field and not pk_set:
    255290                setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
    256291        transaction.commit_unless_managed()
     292       
     293        # write this instance into cache if not already present
     294        if self._meta.has_auto_field:
     295            self.__instance_cache__[self._get_pk_val()] = self
    257296
    258297        # Run any post-save hooks.
    259298        dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
     
    315354        self._collect_sub_objects(seen_objs)
    316355
    317356        # Actually delete the objects
     357        if self._meta.has_auto_field:
     358            pk = self._get_pk_val()
     359            if pk is not None:
     360                self.__instance_cache__.pop(pk, None)
    318361        delete_objects(seen_objs)
    319362
    320363    delete.alters_data = True
  • django/db/models/fields/related.py

     
    165165                if self.field.null:
    166166                    return None
    167167                raise self.field.rel.to.DoesNotExist
    168             other_field = self.field.rel.get_related_field()
    169             if other_field.rel:
    170                 params = {'%s__pk' % self.field.rel.field_name: val}
    171             else:
    172                 params = {'%s__exact' % self.field.rel.field_name: val}
    173             rel_obj = self.field.rel.to._default_manager.get(**params)
     168            rel_obj = self.field.rel.to.get_cached_instance(val)
     169            if rel_obj is None:
     170                other_field = self.field.rel.get_related_field()
     171                if other_field.rel:
     172                    params = {'%s__pk' % self.field.rel.field_name: val}
     173                else:
     174                    params = {'%s__exact' % self.field.rel.field_name: val}
     175                rel_obj = self.field.rel.to._default_manager.get(**params)
    174176            setattr(instance, cache_name, rel_obj)
    175177            return rel_obj
    176178
  • django/db/models/query.py

     
    11091109    for cls in ordered_classes:
    11101110        seen_objs[cls] = seen_objs[cls].items()
    11111111        seen_objs[cls].sort()
     1112        clean_instance_cache = cls.__instance_cache__.pop
    11121113
    11131114        # Pre notify all instances to be deleted
    11141115        for pk_val, instance in seen_objs[cls]:
    11151116            dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
    11161117
    11171118        pk_list = [pk for pk,instance in seen_objs[cls]]
     1119        # we wipe the cache now; it's *possible* some form of a __get__ lookup
     1120        # may reintroduce an item after the fact with the same pk, however
     1121        # it is extremely unlikely
     1122        for x in pk_list:
     1123            clean_instance_cache(x, None)
     1124           
    11181125        for related in cls._meta.get_all_related_many_to_many_objects():
    11191126            if not isinstance(related.field, generic.GenericRelation):
    11201127                for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
     
    11481155    for cls in ordered_classes:
    11491156        seen_objs[cls].reverse()
    11501157        pk_list = [pk for pk,instance in seen_objs[cls]]
     1158        for x in pk_list:
     1159            clean_instance_cache(x, None)
    11511160        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11521161            cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
    11531162                (qn(cls._meta.db_table), qn(cls._meta.pk.column),
  • django/core/serializers/xml_serializer.py

     
    177177                    value = field.to_python(getInnerText(field_node).strip())
    178178                data[field.name] = value
    179179
     180        data["disable_instance_cache"] = True
    180181        # Return a DeserializedObject so that the m2m data has a place to live.
    181182        return base.DeserializedObject(Model(**data), m2m_data)
    182183
  • django/core/serializers/python.py

     
    8888            # Handle all other fields
    8989            else:
    9090                data[field.name] = field.to_python(field_value)
    91 
     91       
     92        data["disable_instance_cache"] = True
    9293        yield base.DeserializedObject(Model(**data), m2m_data)
    9394
    9495def _get_model(model_identifier):
  • tests/modeltests/select_related/models.py

     
    1071071
    108108
    109109# select_related() also of course applies to entire lists, not just items.
    110 # Without select_related()
     110# Without select_related() (note instance caching still reduces this from 9 to 5)
    111111>>> db.reset_queries()
    112112>>> world = Species.objects.all()
    113113>>> [o.genus.family for o in world]
    114114[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>]
    115115>>> len(db.connection.queries)
    116 9
     1165
    117117
    118118# With select_related():
    119119>>> db.reset_queries()
     
    129129>>> pea.genus.family.order.klass.phylum.kingdom.domain
    130130<Domain: Eukaryota>
    131131
    132 # Notice: one few query than above because of depth=1
     132# Notice: instance caching saves the day; would be 7 without.
    133133>>> len(db.connection.queries)
    134 7
     1341
    135135
    136136>>> db.reset_queries()
    137137>>> pea = Species.objects.select_related(depth=5).get(name="sativum")
    138138>>> pea.genus.family.order.klass.phylum.kingdom.domain
    139139<Domain: Eukaryota>
    140140>>> len(db.connection.queries)
    141 3
     1411
    142142
    143143>>> db.reset_queries()
    144144>>> world = Species.objects.all().select_related(depth=2)
    145145>>> [o.genus.family.order for o in world]
    146146[<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>]
    147147>>> len(db.connection.queries)
    148 5
     1481
    149149
    150150# Reset DEBUG to where we found it.
    151151>>> settings.DEBUG = False
Back to Top