Ticket #17: instance-caching-5.patch

File instance-caching-5.patch, 12.8 KB (added by (removed), 17 years ago)

add in a class attr able to disable instance caching for that model.

  • django/core/serializers/python.py

    === modified file 'django/core/serializers/python.py'
     
    8888            # Handle all other fields
    8989            else:
    9090                data[field.name] = field.to_python(field_value)
    91 
     91        data["disable_inst_caching"] = True
    9292        yield base.DeserializedObject(Model(**data), m2m_data)
    9393
    9494def _get_model(model_identifier):
  • django/core/serializers/xml_serializer.py

    === modified file 'django/core/serializers/xml_serializer.py'
     
    176176                else:
    177177                    value = field.to_python(getInnerText(field_node).strip())
    178178                data[field.name] = value
    179 
     179        data["disable_inst_caching"] = True
    180180        # Return a DeserializedObject so that the m2m data has a place to live.
    181181        return base.DeserializedObject(Model(**data), m2m_data)
    182182
     
    234234        else:
    235235           pass
    236236    return u"".join(inner_text)
    237 
  • django/db/models/base.py

    === modified file '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
     
    7778        # registered version.
    7879        return get_model(new_class._meta.app_label, name, False)
    7980
     81    def __call__(cls, *args, **kwargs):
     82        if not kwargs.pop('disable_inst_caching', False) and not cls.disable_inst_caching:
     83            key = cls._get_cache_key(args, kwargs)
     84            if key is not None:
     85                obj = cls.__instance_cache__.get(key)
     86                if obj is None:
     87                    obj = super(ModelBase, cls).__call__(*args, **kwargs)
     88                    cls.__instance_cache__[key] = obj
     89            else:
     90                obj = super(ModelBase, cls).__call__(*args, **kwargs)
     91        else:
     92            obj = super(ModelBase, cls).__call__(*args, **kwargs)
     93        return obj
     94
     95
    8096class Model(object):
    8197    __metaclass__ = ModelBase
    8298
     
    97113    def __ne__(self, other):
    98114        return not self.__eq__(other)
    99115
     116    def _get_cache_key(cls, args, kwargs):
     117        # this should be calculated *once*, but isn't atm
     118        pk_position = cls._meta.fields.index(cls._meta.pk)
     119        if len(args) > pk_position:
     120            return args[pk_position]
     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, id):
     130        return cls.__instance_cache__.get(id)
     131    get_cached_instance = classmethod(get_cached_instance)
     132
    100133    def __init__(self, *args, **kwargs):
    101134        dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
    102135
     
    197230        if hasattr(cls, 'get_absolute_url'):
    198231            cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url)
    199232
     233        cls.__instance_cache__ = WeakValueDictionary()
     234        cls.disable_inst_caching = getattr(cls, 'disable_inst_caching', False) or not cls._meta.has_auto_field
     235
    200236        dispatcher.send(signal=signals.class_prepared, sender=cls)
    201237
    202238    _prepare = classmethod(_prepare)
     
    255291                setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
    256292        transaction.commit_unless_managed()
    257293
     294        # if we're a new instance that hasn't been written in; save ourself.
     295        if not self.disable_inst_caching:
     296            self.__instance_cache__[self._get_pk_val()] = self
     297
    258298        # Run any post-save hooks.
    259299        dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
    260300
     
    315355        self._collect_sub_objects(seen_objs)
    316356
    317357        # Actually delete the objects
     358        if not self.disable_inst_caching:
     359            pk = self._get_pk_val()
     360            if pk is not None:
     361                self.__instance_cache__.pop(pk, None)
    318362        delete_objects(seen_objs)
    319363
    320364    delete.alters_data = True
  • django/db/models/fields/related.py

    === modified file '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

    === modified file 'django/db/models/query.py'
     
    11171117    for cls in ordered_classes:
    11181118        seen_objs[cls] = seen_objs[cls].items()
    11191119        seen_objs[cls].sort()
     1120        clean_inst_cache = cls.__instance_cache__.pop
    11201121
    11211122        # Pre notify all instances to be deleted
    11221123        for pk_val, instance in seen_objs[cls]:
    11231124            dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
    11241125
    11251126        pk_list = [pk for pk,instance in seen_objs[cls]]
     1127        # we wipe the cache now; it's *possible* some form of a __get__ lookup may reintroduce an item after
     1128        # the fact with the same pk (extremely unlikely)
     1129        for x in pk_list:
     1130            clean_inst_cache(x, None)
     1131
    11261132        for related in cls._meta.get_all_related_many_to_many_objects():
    11271133            if not isinstance(related.field, generic.GenericRelation):
    1128                 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
     1134                for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11291135                    cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
    11301136                        (qn(related.field.m2m_db_table()),
    11311137                            qn(related.field.m2m_reverse_name()),
    1132                             ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
    1133                         pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
     1138                            ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
     1139                            pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
     1140
    11341141        for f in cls._meta.many_to_many:
    11351142            if isinstance(f, generic.GenericRelation):
    11361143                from django.contrib.contenttypes.models import ContentType
     
    11391146            else:
    11401147                query_extra = ''
    11411148                args_extra = []
    1142             for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
     1149            for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11431150                cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \
    11441151                    (qn(f.m2m_db_table()), qn(f.m2m_column_name()),
    11451152                    ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra,
    11461153                    pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra)
     1154
    11471155        for field in cls._meta.fields:
    11481156            if field.rel and field.null and field.rel.to in seen_objs:
    1149                 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
     1157                for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11501158                    cursor.execute("UPDATE %s SET %s=NULL WHERE %s IN (%s)" % \
    11511159                        (qn(cls._meta.db_table), qn(field.column), qn(cls._meta.pk.column),
    11521160                            ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
     
    11561164    for cls in ordered_classes:
    11571165        seen_objs[cls].reverse()
    11581166        pk_list = [pk for pk,instance in seen_objs[cls]]
    1159         for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
     1167        clean_inst_cache = cls.__instance_cache__.pop
     1168        for x in pk_list:
     1169            clean_inst_cache(x, None)
     1170        for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11601171            cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
    11611172                (qn(cls._meta.db_table), qn(cls._meta.pk.column),
    11621173                ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
  • tests/modeltests/basic/models.py

    === modified file 'tests/modeltests/basic/models.py'
     
    361361__test__['API_TESTS'] += """
    362362
    363363# You can manually specify the primary key when creating a new object.
    364 >>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
     364>>> a101 = Article(id=101, headline=u'Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
    365365>>> a101.save()
    366366>>> a101 = Article.objects.get(pk=101)
    367367>>> a101.headline
  • tests/modeltests/custom_columns/models.py

    === modified file 'tests/modeltests/custom_columns/models.py'
     
    4040
    4141__test__ = {'API_TESTS':"""
    4242# Create a Author.
    43 >>> a = Author(first_name='John', last_name='Smith')
     43>>> a = Author(first_name=u'John', last_name=u'Smith')
    4444>>> a.save()
    4545
    4646>>> a.id
  • tests/modeltests/generic_relations/models.py

    === modified file 'tests/modeltests/generic_relations/models.py'
     
    8787
    8888# Recall that the Mineral class doesn't have an explicit GenericRelation
    8989# defined. That's OK, because you can create TaggedItems explicitly.
    90 >>> tag1 = TaggedItem(content_object=quartz, tag="shiny")
    91 >>> tag2 = TaggedItem(content_object=quartz, tag="clearish")
     90>>> tag1 = TaggedItem(content_object=quartz, tag=u"shiny")
     91>>> tag2 = TaggedItem(content_object=quartz, tag=u"clearish")
    9292>>> tag1.save()
    9393>>> tag2.save()
    9494
  • tests/modeltests/many_to_one/models.py

    === modified file 'tests/modeltests/many_to_one/models.py'
     
    2727
    2828__test__ = {'API_TESTS':"""
    2929# Create a few Reporters.
    30 >>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
     30>>> r = Reporter(first_name=u'John', last_name=u'Smith', email='john@example.com')
    3131>>> r.save()
    3232
    33 >>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
     33>>> r2 = Reporter(first_name=u'Paul', last_name=u'Jones', email='paul@example.com')
    3434>>> r2.save()
    3535
    3636# Create an Article.
  • tests/modeltests/select_related/models.py

    === modified file '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