Как получить экземпляр производного класса в методе абстрактного базового класса без явного импорта производного класса в 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
...чтобы я мог скрыть детали производного класса и применить этот абстрактный базовый класс к любому набору моделей?
Независимо от альтернативных стратегий, решающих проблему кэширования, я просто хотел бы знать, как правильно написать абстрактный класс, который выполняет действия со связанными с ним производными классами без необходимости импортировать эти классы в файл абстрактного класса.