Ticket #17: 5737_instance_caching.diff
File 5737_instance_caching.diff, 8.6 KB (added by , 17 years ago) |
---|
-
django/db/models/base.py
15 15 from django.utils.encoding import smart_str, force_unicode 16 16 from django.conf import settings 17 17 from itertools import izip 18 from weakref import WeakValueDictionary 18 19 import types 19 20 import sys 20 21 import os … … 76 77 # should only be one class for each model, so we must always return the 77 78 # registered version. 78 79 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 79 95 80 96 class Model(object): 81 97 __metaclass__ = ModelBase … … 96 112 97 113 def __ne__(self, other): 98 114 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) 99 132 100 133 def __init__(self, *args, **kwargs): 101 134 dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs) … … 196 229 197 230 if hasattr(cls, 'get_absolute_url'): 198 231 cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url) 232 233 cls.__instance_cache__ = WeakValueDictionary() 199 234 200 235 dispatcher.send(signal=signals.class_prepared, sender=cls) 201 236 … … 254 289 if self._meta.has_auto_field and not pk_set: 255 290 setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) 256 291 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 257 296 258 297 # Run any post-save hooks. 259 298 dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self) … … 315 354 self._collect_sub_objects(seen_objs) 316 355 317 356 # 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) 318 361 delete_objects(seen_objs) 319 362 320 363 delete.alters_data = True -
django/db/models/fields/related.py
165 165 if self.field.null: 166 166 return None 167 167 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) 174 176 setattr(instance, cache_name, rel_obj) 175 177 return rel_obj 176 178 -
django/db/models/query.py
1109 1109 for cls in ordered_classes: 1110 1110 seen_objs[cls] = seen_objs[cls].items() 1111 1111 seen_objs[cls].sort() 1112 clean_instance_cache = cls.__instance_cache__.pop 1112 1113 1113 1114 # Pre notify all instances to be deleted 1114 1115 for pk_val, instance in seen_objs[cls]: 1115 1116 dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance) 1116 1117 1117 1118 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 1118 1125 for related in cls._meta.get_all_related_many_to_many_objects(): 1119 1126 if not isinstance(related.field, generic.GenericRelation): 1120 1127 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): … … 1148 1155 for cls in ordered_classes: 1149 1156 seen_objs[cls].reverse() 1150 1157 pk_list = [pk for pk,instance in seen_objs[cls]] 1158 for x in pk_list: 1159 clean_instance_cache(x, None) 1151 1160 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 1152 1161 cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ 1153 1162 (qn(cls._meta.db_table), qn(cls._meta.pk.column), -
django/core/serializers/xml_serializer.py
177 177 value = field.to_python(getInnerText(field_node).strip()) 178 178 data[field.name] = value 179 179 180 data["disable_instance_cache"] = True 180 181 # Return a DeserializedObject so that the m2m data has a place to live. 181 182 return base.DeserializedObject(Model(**data), m2m_data) 182 183 -
django/core/serializers/python.py
88 88 # Handle all other fields 89 89 else: 90 90 data[field.name] = field.to_python(field_value) 91 91 92 data["disable_instance_cache"] = True 92 93 yield base.DeserializedObject(Model(**data), m2m_data) 93 94 94 95 def _get_model(model_identifier): -
tests/modeltests/select_related/models.py
107 107 1 108 108 109 109 # 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) 111 111 >>> db.reset_queries() 112 112 >>> world = Species.objects.all() 113 113 >>> [o.genus.family for o in world] 114 114 [<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] 115 115 >>> len(db.connection.queries) 116 9 116 5 117 117 118 118 # With select_related(): 119 119 >>> db.reset_queries() … … 129 129 >>> pea.genus.family.order.klass.phylum.kingdom.domain 130 130 <Domain: Eukaryota> 131 131 132 # Notice: one few query than above because of depth=1132 # Notice: instance caching saves the day; would be 7 without. 133 133 >>> len(db.connection.queries) 134 7 134 1 135 135 136 136 >>> db.reset_queries() 137 137 >>> pea = Species.objects.select_related(depth=5).get(name="sativum") 138 138 >>> pea.genus.family.order.klass.phylum.kingdom.domain 139 139 <Domain: Eukaryota> 140 140 >>> len(db.connection.queries) 141 3 141 1 142 142 143 143 >>> db.reset_queries() 144 144 >>> world = Species.objects.all().select_related(depth=2) 145 145 >>> [o.genus.family.order for o in world] 146 146 [<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>] 147 147 >>> len(db.connection.queries) 148 5 148 1 149 149 150 150 # Reset DEBUG to where we found it. 151 151 >>> settings.DEBUG = False