python django django-models django-orm django-cache-machine

python - Determine si un atributo es un `DeferredAttribute` en django



django-models django-orm (2)

El contexto

He localizado un error bastante crítico en la Máquina de caché de Django que hace que su lógica de invalidación se vuelva loca después de una actualización de Django 1.4 a 1.7.

El error se localiza en invocaciones de only() en modelos que extienden CachingMixin la máquina de CachingMixin . Resulta en recursiones profundas que ocasionalmente rompen la pila, pero por lo demás crean enormes flush_lists que la máquina de caché usa para la invalidación bidireccional para modelos en relaciones ForeignKey .

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

El bicho

El error se produce en las siguientes líneas ( https://github.com/jbalogh/django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254 ). En este caso, self es una instancia de MyModel con una combinación de atributos diferidos y no diferidos:

fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields if isinstance(f, models.ForeignKey))

La máquina de caché realiza la invalidación bidireccional en las relaciones de ForeignKey . Para ello, recorre todos los campos de un Model y almacena una serie de punteros en la memoria caché que apuntan a objetos que deben invalidarse cuando el objeto en cuestión se invalida.

El uso de only() en el ORM de Django hace algo de programación mágica que anula los atributos no alcanzados con la implementación del atributo DeferredAttribute de Django. En circunstancias normales, un acceso a favorite_color invocaría a DeferredAttribute.__get__ ( https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146 . caché de resultados o la fuente de datos. Para ello, busca la representación no diferida del Model en cuestión y llama a otra consulta only() en él.

Este es el problema cuando se pasa por encima de las claves foráneas en el Model y se accede a sus valores, Cachine Machine introduce una recursión involuntaria. getattr(self, f.attname) en un atributo que se aplaza induce la búsqueda de un Model que tiene CachingMixin aplicado y tiene atributos aplazados. Esto inicia de nuevo todo el proceso de almacenamiento en caché.

La pregunta

Me gustaría abrir un PR para solucionar este problema y creo que la respuesta es tan simple como saltarse los atributos diferidos, pero no estoy seguro de cómo hacerlo porque el acceso al atributo hace que se inicie el proceso de búsqueda.

Si todo lo que tengo es un identificador de una instancia de un Model con una combinación de atributos diferidos y no diferidos, ¿hay una manera de determinar si un atributo es un atributo DeferredAttribute sin acceder a él?

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



Esto comprobará si el atributo es un atributo diferido y aún no se ha cargado desde la base de datos:

fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))

Internamente, type(self) es un modelo Proxy recién creado para la clase original. Un DeferredAttribute primero verifica el dictado local de la instancia. Si eso no existe, cargará el valor de la base de datos. Este método omite el descriptor del objeto DeferredAttribute para que el valor no se cargue si no existe.

Esto funciona en Django 1.4 y 1.7, y presumiblemente en las versiones intermedias. Tenga en cuenta que Django 1.8 introducirá a su debido tiempo el método get_deferred_fields() que reemplazará todo este entrometimiento con los elementos internos de la clase.