Как получить экземпляр производного класса в методе абстрактного базового класса без явного импорта производного класса в Django

Конечная цель: Я создаю абстрактный базовый класс HierCachedModel(django.models.Model). Его цель - выполнять накладные расходы, необходимые для поддержания постоянного кэша базы данных серии функций, которые находятся в 4 иерархически связанных классах Model. По сути, он сохраняет кэшированное значение при каждом обращении к любой "кэшированной функции" в этом классе и перекрывает методы Model save() и delete() для выполнения связанных с кэшем накладных операций, таких как каскадное удаление всех кэшированных значений под "корневым" экземпляром модели. Он также имеет набор вспомогательных методов, которые могут быть вызваны для выполнения "глобальных" операций, связанных с кэшем (например, заполнение недостающих кэшированных значений или удаление всех кэшей под заданным набором или корневыми объектами модели).

Все работает хорошо, за исключением того, что, как я это сделал (и я уверен, что это просто от неопытности), я должен импортировать производные классы в файл абстрактного базового класса, который я создал (hier_cached_model.py), иначе я получаю ошибки о неопределенных классах каждый раз, когда я выполняю что-либо, что проходит через иерархию.

До этой реализации кэширования у меня были такие классы (я сделаю 3 класса в качестве примера для упрощения):

models.py:

class Animal(Model):
    ...

class Sample(Model):
    animal = models.ForeignKey(...  related_name="samples")
    ...

class Analysis(Model):
    sample = models.ForeignKey(...  related_name="analyses")
    ...

Затем я создал hier_cached_model.py, который имеет кучу методов для выполнения всех накладных операций с кэшем, но причина, по которой он является базовым классом - это ядро функциональности:

class HierCachedModel(Model):
    parent_related_key_name: Optional[str] = None
    child_related_key_names: Optional[List[str]] = []

    def save(self, *args, **kwargs):
        """
        Trigger a cascading deletion of every cached value under the linked root (e.g. Animal) record
        """
        if self.parent_related_key_name is not None:
            parent_instance = getattr(self, self.parent_related_key_name)
            parent_instance.delete_cache()
        else:
            self.delete_cache()
        super().save(*args, **kwargs)  # Call the "real" save() method.

    def delete(self, *args, **kwargs):
        """
        Trigger a cascading deletion of every cached value under the linked root model (e.g. Animal) record
        """
        if self.parent_related_key_name is not None:
            parent_instance = getattr(self, self.parent_related_key_name)
            parent_instance.delete_cache()
        else:
            self.delete_cache()
        super().delete(*args, **kwargs)  # Call the "real" delete() method.

    class Meta:
        abstract = True

Чтобы использовать этот новый класс в качестве базового, мой исходный models.py теперь выглядит так:

class Animal(HierCachedModel):
    # No parent_related_key_name, because this is a root
    child_related_key_names = ["samples"]
    ...

class Sample(HierCachedModel):
    parent_related_key_name = "animal"
    child_related_key_names = ["analyses"]
    animal = models.ForeignKey(...  related_name="samples")
    ...

class Analysis(HierCachedModel):
    parent_related_key_name = "sample"
    # Leaf
    sample = models.ForeignKey(...  related_name="analyses")
    ...

Проблема существует в hier_cached_model.py, когда я пытаюсь инициировать каскадное обслуживание кэша для манипулирования всеми кэшированными значениями под корневым экземпляром модели, но чтобы не усложнять, я приведу третий сценарий утилиты в качестве примера, где я должен сделать тот же импорт, которого я хотел бы избежать как в этом сценарии утилиты, так и в hier_cached_model.py...

build_caches.py

from DataRepo.models import Animal, Sample, Analysis
# ^^^ This is the import I feel I shouldn't have to do if I was doing this "right"

func_name_lists = get_cached_method_names()
# ^^^ This is a global in hier_cached_model.py where I keep data in a dict: {class_name: [list of cached functions in that class], ...}

for class_name in func_name_lists.keys():
    cls = eval(class_name)
    # ^^^ This is the line that complains that either Animal, Sample, or Analysis is undefined
    for cfunc_name in func_name_lists[class_name]:
        print(f"Building {class_name}.{cfunc_name} caches")
        cached_function_call(cls, cfunc_name)

def cached_function_call(cls, cfunc_name):
    """
    Iterates over every record in the database and caches the value for the supplied cached_function if it's not cached
    """
    for rec in cls.objects.all():
        getattr(rec, cfunc_name)
    return True

Здесь гораздо больше возможностей. Например, я использую декоратор, который фиксирует имена кэшируемых функций и классов, к которым они принадлежат, а возвращаемая им функция обертывает функцию, переданную декоратору, и эта возвращаемая функция получает/устанавливает значения кэша. Ключи кэша состоят из имени производного класса, первичного ключа и имени функции, результаты которой кэшируются.

Как мне сделать это, чтобы файл абстрактного базового класса (HierCachedModel) (hier_cached_model.py) и скрипт утилиты (build_cached.py) не делали этот импорт:

from DataRepo.models import Animal, Sample, Analysis

...чтобы я мог скрыть детали производного класса и применить этот абстрактный базовый класс к любому набору моделей?

Независимо от альтернативных стратегий, решающих проблему кэширования, я просто хотел бы знать, как правильно написать абстрактный класс, который выполняет действия со связанными с ним производными классами без необходимости импортировать эти классы в файл абстрактного класса.

Вернуться на верх