Django

Code

Ticket #17: instance-caching-5.patch

File instance-caching-5.patch, 12.8 kB (added by Brian Harring <ferringb@gmail.com>, 1 year ago)

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

  • 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        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

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

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

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

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

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

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

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

    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