Python Django-如何可视化信号并保存覆盖?
随着项目的增长,依赖项和事件链也在增长,特别是在重写的Python Django-如何可视化信号并保存覆盖?,python,django,architecture,software-design,Python,Django,Architecture,Software Design,随着项目的增长,依赖项和事件链也在增长,特别是在重写的save()方法和post\u save和pre\u save信号中 例如: 被重写的A.save将创建两个与A-B和C相关的对象。保存C时,会调用执行其他操作的post\u save信号,以此类推 如何才能更清楚地表达这些事件?是否有办法可视化(自动生成)此类链/流?我不是在寻找ERD或Class图。我需要确保在一个地方做一件事不会影响项目的另一方,所以简单的可视化将是最好的 编辑 要清楚,我知道几乎不可能检查动态生成的信号。我只想检查所有
save()
方法和post\u save
和pre\u save
信号中
例如:
被重写的A.save
将创建两个与A
-B
和C
相关的对象。保存C
时,会调用执行其他操作的post\u save
信号,以此类推
如何才能更清楚地表达这些事件?是否有办法可视化(自动生成)此类链/流?我不是在寻找ERD
或Class
图。我需要确保在一个地方做一件事不会影响项目的另一方,所以简单的可视化将是最好的
编辑
要清楚,我知道几乎不可能检查动态生成的信号。我只想检查所有(非动态生成的)
post\u save
,pre\u save
,并覆盖save
方法,并将它们可视化,以便我可以立即看到发生了什么,以及在保存时在哪里 这不是完整的解决方案,但我希望它能成为一个良好的起点。考虑这个代码:
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class A(models.Model):
def save(self, *args, **kwargs):
if not self.pk:
C.objects.create()
class B(models.Model):
pass
class C(models.Model):
b = models.ForeignKey(B, on_delete=models.CASCADE, blank=True)
@receiver(pre_save, sender=C)
def pre_save_c(sender, instance, **kwargs):
if not instance.pk:
b = B.objects.create()
instance.b = b
我们可以通过以下方式使用inspect
、djangoget_models()
和signals
获取应用程序名称列表的依赖项:
import inspect
import re
from collections import defaultdict
from django.apps import apps
from django.db.models import signals
RECEIVER_MODELS = re.compile('sender=(\w+)\W')
SAVE_MODELS = re.compile('(\w+).objects.')
project_signals = defaultdict(list)
for signal in vars(signals).values():
if not isinstance(signal, signals.ModelSignal):
continue
for _, receiver in signal.receivers:
rcode = inspect.getsource(receiver())
rmodel = RECEIVER_MODELS.findall(rcode)
if not rmodel:
continue
auto_by_signals = [
'{} auto create -> {}'.format(rmodel[0], cmodel)
for cmodel in SAVE_MODELS.findall(rcode)
]
project_signals[rmodel[0]].extend(auto_by_signals)
for model in apps.get_models():
is_self_save = 'save' in model().__class__.__dict__.keys()
if is_self_save:
scode = inspect.getsource(model.save)
model_name = model.__name__
for cmodel in SAVE_MODELS.findall(scode):
print('{} auto create -> {}'.format(model_name, cmodel))
for smodels in project_signals.get(cmodel, []):
print(smodels)
这使得:
A auto create -> C
C auto create -> B
更新:将方法更改为被实例类dict覆盖的foundsave
is_self_save = 'save' in model().__class__.__dict__.keys()
(评论太长,没有完整的代码)
我现在无法模拟大量的代码,但另一个有趣的解决方案,受Mario Orlandi上述评论的启发,将是某种脚本,它扫描整个项目,搜索任何重写的保存方法和保存前、后信号,跟踪创建它们的类/对象。它可以像一系列正则表达式一样简单,这些正则表达式查找class
定义,然后在定义中添加任何重写的save
方法
扫描完所有内容后,可以使用此引用集合根据类名创建依赖关系树(或一组树),然后对每个树进行拓扑排序。任何连接的组件都可以说明依赖关系,您可以可视化或搜索这些树,以非常简单、自然的方式查看依赖关系。我对django比较幼稚,但似乎可以通过这种方式静态跟踪依赖关系,除非这些方法在不同的时间在多个位置被重写是很常见的 如果您只想跟踪模型保存,而对重写的保存方法和信号中发生的其他事情不感兴趣,那么可以使用类似angio的机制。您可以注册一个全局post_save receiver(无sender参数),该接收器将为所有模型保存调用,并在该函数中打印保存的模型名称。然后,编写一个脚本,为所有现有模型调用save。类似以下的方法可能会起作用:
@receiver(models.signals.post_save)
def global_post_save(sender, instance, created, *args, **kwargs):
print(' --> ' + str(sender.__name__))
from django.apps import apps
for model in apps.get_models():
instance = model.objects.first()
if instance:
print('Saving ' + str(model.__name__))
instance.save()
print('\n\n')
具有以下模型结构
class A(models.Model):
...
def save(self, *args, **kwargs):
B.objects.create()
@receiver(post_save, sender=B)
def post_save_b(sender, instance, **kwargs):
C.objects.create()
脚本将打印:
Saving A
--> A
--> B
--> C
Saving B
--> B
--> C
Saving C
--> C
这只是一个可以完成的基本草图,可以根据应用程序的结构进行改进。这假定每个模型的数据库中都有一个条目。尽管不改变任何内容,但这种方法也会将内容保存在数据库中,因此最好在测试数据库上运行。假设您的最终目标是在保存某个模型的实例时跟踪数据库中的更改,一种可能的解决方案是扫描数据库中的更改,而不是源代码。这种方法的优点是它还可以覆盖动态代码。缺点是,很明显,它只会覆盖数据库更改
这可以通过使用简单的测试技术来实现。假设以下模型
from django.db import models
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
class B(models.Model):
def save(self, *args, **kwargs):
X.objects.create()
super().save(*args, **kwargs)
class C(models.Model):
y = models.OneToOneField('Y', on_delete=models.CASCADE)
class D(models.Model):
pass
class X(models.Model):
pass
class Y(models.Model):
related = models.ForeignKey('Z', on_delete=models.CASCADE)
class Z(models.Model):
pass
@receiver(pre_save, sender=D)
def pre_save_d(*args, instance, **kwargs):
Z.objects.create()
@receiver(post_save, sender=C)
def pre_save_c(*args, instance, **kwargs):
Y.objects.create(related=Z.objects.create())
我可以编写一个测试用例,对所有数据库实例进行计数,创建一个模型实例,再次计数并计算差异。可以使用工厂创建数据库实例,如。下面是这项技术的一个简单但有效的例子
class TestModelDependency(TestCase):
def test_dependency(self):
models = apps.get_models()
models = [model for model in models if model._meta.app_label == 'model_effects']
for model in models:
kwargs = self.get_related_attributes(model)
initial_count = self.take_count(models)
mommy.make(model, **kwargs)
final_count = self.take_count(models)
diff = self.diff(initial_count, final_count)
print(f'Creating {model._meta.model_name}')
print(f'Created {" | ".join(f"{v} instance of {k}" for k, v in diff.items())}')
call_command('flush', interactive=False)
@staticmethod
def take_count(models):
return {model._meta.model_name: model.objects.count() for model in models}
@staticmethod
def diff(initial, final):
result = dict()
for k, v in final.items():
i = initial[k]
d = v - i
if d != 0:
result[k] = d
return result
@staticmethod
def get_related_attributes(model):
kwargs = dict()
for field in model._meta.fields:
if any(isinstance(field, r) for r in [ForeignKey, OneToOneField]):
kwargs[field.name] = mommy.make(field.related_model)
return kwargs
我的输出是
Creating b
Created 1 instance of b | 1 instance of x
Creating c
Created 1 instance of c | 1 instance of y | 1 instance of z
Creating d
Created 1 instance of d | 1 instance of z
Creating x
Created 1 instance of x
Creating y
Created 1 instance of y
Creating z
Created 1 instance of z
对于大型应用程序,它可能会很慢,但我使用内存中的sqlite数据库进行测试,它运行得非常快。我正在使用一个Django应用程序,该应用程序也有类似的功能,但当我完成时,我将对您在此处介绍的用例进行评论:
我需要确保在一个地方做一件事不会影响项目的另一方
您当然可以使用一些虚拟信号处理程序编写测试,以了解特定代码的执行是否会触发不需要的行为,例如:
# I use pytest, put this example is suitable also for
# django's TestCase and others
class TestSome:
# For Django TestCase this would be setUp
def setup_method(self, test_method):
self.singals_info = []
def dummy_handler(*args, **kwargs):
# collect_info is a function you must implement, it would
# gather info about signal, sender, instance, etc ... and
# save that info in (for example) self.signals_info.
# You can then use that info for test assertions.
self.collect_info(*args, **kwargs)
# connect your handler to every signal you want to control
post_save.connect(dummy_handler)
def test_foo():
# Your normal test here ...
some_value = some_tested_function()
# Check your signals behave
assert self.signals_behave(self.signals_info)
为什么这比有一个显示事件链的脚本要好?
正如你所说,当需要这样的东西时,是因为项目的规模很大,如果你使用你想要的工具,你可以这样结束:
Save A -> Creates B -> Creates C
Save B -> Creates D
Save B -> Creates C
.
.
.
# Imagine here 3 or 4 more lines.
每次你想添加一些代码来保存/修改一些东西时,你最终都会解决一个难题
然而。。。
最好是编写代码,然后一些测试失败(为您解决难题),并向您准确地显示代码的错误行为
结论:
实施这些测试,你的生活会更轻松
使用测试的最佳场景:编写您的代码,如果测试没有失败,您就准备好处理下一个编程任务
使用测试的最坏情况:编写您的代码,一些测试失败,因为您知道您的代码到底在哪里坏了,只需修复它即可
使用工具的最佳方案:分析工具输出,编写代码,一切正常
使用工具的最坏情况:分析工具输出,编写代码,如果出现故障,请重复,直到一切正常
那么,像这样的工具会有帮助吗?当然,但这不是正确的工具