Django

Code

Ticket #17: 5737_instance_caching.diff

File 5737_instance_caching.diff, 8.6 kB (added by Brian Rosner <brosner@gmail.com>, 1 year ago)

updated patch to #5737 with some minor edits

  • django/db/models/base.py

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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 
     116
    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 
     134
    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 
     141
    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 
     148
    149149 
    150150# Reset DEBUG to where we found it. 
    151151>>> settings.DEBUG = False