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
、django
get_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覆盖的found
save

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.
每次你想添加一些代码来保存/修改一些东西时,你最终都会解决一个难题

然而。。。 最好是编写代码,然后一些测试失败(为您解决难题),并向您准确地显示代码的错误行为

结论: 实施这些测试,你的生活会更轻松

使用测试的最佳场景:编写您的代码,如果测试没有失败,您就准备好处理下一个编程任务

使用测试的最坏情况:编写您的代码,一些测试失败,因为您知道您的代码到底在哪里坏了,只需修复它即可

使用工具的最佳方案:分析工具输出,编写代码,一切正常

使用工具的最坏情况:分析工具输出,编写代码,如果出现故障,请重复,直到一切正常

那么,像这样的工具会有帮助吗?当然,但这不是正确的工具