Ticket #17: instance-caching-5.patch
| File instance-caching-5.patch, 12.8 kB (added by Brian Harring <ferringb@gmail.com>, 1 year ago) |
|---|
-
django/core/serializers/python.py
old new 88 88 # Handle all other fields 89 89 else: 90 90 data[field.name] = field.to_python(field_value) 91 91 data["disable_inst_caching"] = True 92 92 yield base.DeserializedObject(Model(**data), m2m_data) 93 93 94 94 def _get_model(model_identifier): -
django/core/serializers/xml_serializer.py
old new 176 176 else: 177 177 value = field.to_python(getInnerText(field_node).strip()) 178 178 data[field.name] = value 179 179 data["disable_inst_caching"] = True 180 180 # Return a DeserializedObject so that the m2m data has a place to live. 181 181 return base.DeserializedObject(Model(**data), m2m_data) 182 182 … … 234 234 else: 235 235 pass 236 236 return u"".join(inner_text) 237 -
django/db/models/base.py
old new 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 … … 77 78 # registered version. 78 79 return get_model(new_class._meta.app_label, name, False) 79 80 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 80 96 class Model(object): 81 97 __metaclass__ = ModelBase 82 98 … … 97 113 def __ne__(self, other): 98 114 return not self.__eq__(other) 99 115 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 100 133 def __init__(self, *args, **kwargs): 101 134 dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs) 102 135 … … 197 230 if hasattr(cls, 'get_absolute_url'): 198 231 cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url) 199 232 233 cls.__instance_cache__ = WeakValueDictionary() 234 cls.disable_inst_caching = getattr(cls, 'disable_inst_caching', False) or not cls._meta.has_auto_field 235 200 236 dispatcher.send(signal=signals.class_prepared, sender=cls) 201 237 202 238 _prepare = classmethod(_prepare) … … 255 291 setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) 256 292 transaction.commit_unless_managed() 257 293 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 258 298 # Run any post-save hooks. 259 299 dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self) 260 300 … … 315 355 self._collect_sub_objects(seen_objs) 316 356 317 357 # 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) 318 362 delete_objects(seen_objs) 319 363 320 364 delete.alters_data = True -
django/db/models/fields/related.py
old new 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
old new 1117 1117 for cls in ordered_classes: 1118 1118 seen_objs[cls] = seen_objs[cls].items() 1119 1119 seen_objs[cls].sort() 1120 clean_inst_cache = cls.__instance_cache__.pop 1120 1121 1121 1122 # Pre notify all instances to be deleted 1122 1123 for pk_val, instance in seen_objs[cls]: 1123 1124 dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance) 1124 1125 1125 1126 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 1126 1132 for related in cls._meta.get_all_related_many_to_many_objects(): 1127 1133 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): 1129 1135 cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ 1130 1136 (qn(related.field.m2m_db_table()), 1131 1137 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 1134 1141 for f in cls._meta.many_to_many: 1135 1142 if isinstance(f, generic.GenericRelation): 1136 1143 from django.contrib.contenttypes.models import ContentType … … 1139 1146 else: 1140 1147 query_extra = '' 1141 1148 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): 1143 1150 cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \ 1144 1151 (qn(f.m2m_db_table()), qn(f.m2m_column_name()), 1145 1152 ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra, 1146 1153 pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra) 1154 1147 1155 for field in cls._meta.fields: 1148 1156 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): 1150 1158 cursor.execute("UPDATE %s SET %s=NULL WHERE %s IN (%s)" % \ 1151 1159 (qn(cls._meta.db_table), qn(field.column), qn(cls._meta.pk.column), 1152 1160 ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), … … 1156 1164 for cls in ordered_classes: 1157 1165 seen_objs[cls].reverse() 1158 1166 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): 1160 1171 cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ 1161 1172 (qn(cls._meta.db_table), qn(cls._meta.pk.column), 1162 1173 ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), -
tests/modeltests/basic/models.py
old new 361 361 __test__['API_TESTS'] += """ 362 362 363 363 # 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)) 365 365 >>> a101.save() 366 366 >>> a101 = Article.objects.get(pk=101) 367 367 >>> a101.headline -
tests/modeltests/custom_columns/models.py
old new 40 40 41 41 __test__ = {'API_TESTS':""" 42 42 # Create a Author. 43 >>> a = Author(first_name= 'John', last_name='Smith')43 >>> a = Author(first_name=u'John', last_name=u'Smith') 44 44 >>> a.save() 45 45 46 46 >>> a.id -
tests/modeltests/generic_relations/models.py
old new 87 87 88 88 # Recall that the Mineral class doesn't have an explicit GenericRelation 89 89 # 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") 92 92 >>> tag1.save() 93 93 >>> tag2.save() 94 94 -
tests/modeltests/many_to_one/models.py
old new 27 27 28 28 __test__ = {'API_TESTS':""" 29 29 # 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') 31 31 >>> r.save() 32 32 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') 34 34 >>> r2.save() 35 35 36 36 # Create an Article. -
tests/modeltests/select_related/models.py
old new 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
