Déterminer si un attribut est un `DeferredAttribute` dans django
Le Contexte
j'ai localisé un bug plutôt critique dans la machine de Cache Django qui fait que sa logique d'invalidation perd son esprit après une mise à jour de Django 1.4 à 1.7.
le bug est localisé sur les invocations de only()
sur les modèles qui étendent la machine de cache CachingMixin
. Il en résulte des récursions profondes qui parfois casser la pile, mais sinon créer énorme flush_lists
que cache machine utilise pour bi-directionnel invalidation pour les modèles ForeignKey
relations.
class MyModel(CachingMixin):
id = models.CharField(max_length=50, blank=True)
nickname = models.CharField(max_length=50, blank=True)
favorite_color = models.CharField(max_length=50, blank=True)
content_owner = models.ForeignKey(OtherModel)
m = MyModel.objects.only('id').all()
Le Bug
le bogue se produit dans les cas suivants: lines(https://github.com/jbalogh/django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254). Dans ce cas self
est une instance de MyModel
avec un mélange d'attributs différés et non découragés:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if isinstance(f, models.ForeignKey))
Cache de la Machine ne invalidation bidirectionnelle à travers ForeignKey
relations. Il le fait en bouclant tous les champs dans un Model
et stocker une série de pointeurs dans le cache qui pointent vers des objets qui doivent être invalidés lorsque l'objet en question est invalidé.
only()
dans l'ORM de Django fait un peu de méta-programmation de la magie qui l'emporte sur le unfetched attributs avec Django DeferredAttribute
mise en œuvre. Dans des circonstances normales, un accès à favorite_color
invoquent DeferredAttribute.__get__
(https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146) et récupérez l'attribut soit dans le cache de résultat, soit dans la source de données. Il le fait en récupérant la représentation non découragée de la Model
en question et l'appel d'une autre only()
requête sur elle.
C'est le problème quand boucler sur les clés étrangères dans le Model
et en accédant à leurs valeurs, Cachine Machine introduit un récursion involontaire. getattr(self, f.attname)
sur un attribut qui est reporté induit une extraction d'un Model
qui a l' CachingMixin
appliqué et a des attributs différés. Cela recommence tout le processus de mise en cache.
La Question
je voudrais ouvrir un PR pour corriger ceci et je crois que la réponse à ceci est aussi simple que sauter au-dessus des attributs différés, mais je ne suis pas sûr comment le faire parce que l'accès à l'attribut provoque le extraction de démarrage du processus.
si je n'ai qu'une poignée sur une instance de Model
avec un mélange d'attributs différés et non découragés, y a-t-il un moyen de déterminer si un attribut est un DeferredAttribute
sans accéder?
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and <f's value isn't a Deferred attribute))
2 réponses
Voici comment vérifier si un champ est différé:
from django.db.models.query_utils import DeferredAttribute
is_deferred = isinstance(model_instance.__class__.__dict__.get(field.attname), DeferredAttribute):
extrait de:https://github.com/django/django/blob/1.9.4/django/db/models/base.py#L393
ceci vérifiera si l'attribut est un attribut différé et n'est pas encore chargé à partir de la base de données:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))
en Interne, type(self)
est un nouvellement créé modèle de remplacement pour la classe d'origine. DeferredAttribute
vérifie d'abord le dict local de l'instance. Si cela n'existe pas, il chargera la valeur de la base de données. Cette méthode contourne le DeferredAttribute
descripteur d'objet pour que la valeur ne soit pas chargée si elle n'existe pas.
cela fonctionne dans Django 1.4 et 1.7, et probablement dans les versions intermédiaires. Notez que Django 1.8 introduira le get_deferred_fields()
méthode qui supplantera toute cette ingérence avec les internes de la classe.