Django的override_settings修饰器浅析
- Django的Settings模块代码说明
- Django的
override_settings修饰器分析
前言
前两天刚刚看了Django settings的实现,今天又发现了测试工具中有个override_settings修饰器,于是就想从它下手来分析一下Django的settings。
本篇文章主要分析的是override_settings作为修饰器的实现,其作为上下文管理器的部分并未详细分析。
Django的settings说明
在分析这个修饰器之前,我们先来了解一下Django的settings是如何实现的, Django的settings的代码存放在django/conf/__init__.py中,其中主要有三个类,LazySettings,Settings和UserSettingsHolder。
Settings
Settings类实现了Django配置的载入和存储功能,在它的__init__函数中,首先从django/conf/global_settings.py文件中导入Django默认的配置,然后再从DJANGO_SETTINGS_MODULE环境变量所指定的配置文件中导入我们项目自定义的配置,然后逐项地检查并添加配置。
需要注意的是,Settings会把我们自定义的配置的名称存放到Settings._explicit_settings集合中,Settings.is_overriden函数就是通过检查Settings._explicit_settings集合,来查看某项配置是否被我们自定义的配置给覆盖掉了。
LazySettings
LazySettings是Settings类的一个代理,或者可以认为是对Settings的一层包裹,它的主要功能就是Lazy,或者叫做懒载入。它把Settings类的实例存放在它的私有变量_wrapped中,初始化的时候,它默认将_wrapped设置为空,只有当从LazySettings中取值的时候(会调用LazySettings.__getattr__函数),它才会调用_setup函数,实例化一个Settings对象,并从中取出对应的配置项。
我们平常在代码中使用的django.conf.settings对象,就是这个类的实例。
UserSettingsHolder
UserSettingsHolder类和上面两个类有些不同,它也是用来存储用户的配置项的,只不过它并不会从django/conf/global_settings.py文件中读取Django默认的配置,它只是单纯地将用户传入的配置存储起来,以供读取。
def __init__(self, default_settings):
"""
Requests for configuration variables not in this class are satisfied
from the module specified in default_settings (if possible).
"""
self.__dict__['_deleted'] = set()
self.default_settings = default_settings
上面就是UserSettingsHolder构造函数,从中我们可以看到,它接收一个default_settings参数,它主要就是从这个参数中获取并存储用户要设置的配置,并不存储Django默认的配置。
override_settings修饰器的分析
了解完Django的settings的实现以后,我们再来看一下override_settings修饰器。
我们在使用override_settings作为修饰器的时候,通常的形式是这样的:
@override_settings(DEBUG=False)
def test_some_feature(self):
pass
上面的代码中修饰器的部分,我们可以使用下面这种方式来理解它:
test_some_feature = override_settings(DEBUG=False)(test_some_feature)
从上面的形式可以看出,修饰器语法真正调用的是override_settings类的__call__方法,所以我们需要去关注一下override_settings的__call__方法。但是这个方法在override_settings类中并没有实现,它是在它的父类TestContextDecorator中实现的。
TestContextDecorator
__call__方法
TestContextDecorator类的__call__方法如下所示:
def __call__(self, decorated):
if isinstance(decorated, type):
return self.decorate_class(decorated)
elif callable(decorated):
return self.decorate_callable(decorated)
raise TypeError('Cannot decorate object of type %s' % type(decorated))
从代码中我们可以看出,这个函数的功能主要就是对被修饰的对象进行了一下鉴别,如果修饰的是类,则调用decorate_class函数,如果修饰的是其他的可调用对象,则调用decorate_callable函数。
decorate_class
接着我们来看一下decorate_class函数,它的代码如下:
def decorate_class(self, cls):
if issubclass(cls, TestCase):
decorated_setUp = cls.setUp
decorated_tearDown = cls.tearDown
def setUp(inner_self):
-> context = self.enable()
-> if self.attr_name:
-> setattr(inner_self, self.attr_name, context)
decorated_setUp(inner_self)
def tearDown(inner_self):
decorated_tearDown(inner_self)
-> self.disable()
cls.setUp = setUp
cls.tearDown = tearDown
return cls
raise TypeError('Can only decorate subclasses of unittest.TestCase')
从上面的代码可以看出,decorate_class函数的针对TestCase类的setUp和tearDown函数添加了一些额外的代码,即我用->符号标记的部分。
这些代码的主要功能就是在TestCase类初始化的时候调用TestContextDecorator.enable函数,再以self.attr_name的值作为名称,把enable函数的返回值插入到TestCase类的实例中。同时在TestCase类销毁的时候调用TestContextDecorator.disable函数。
我们接着再去查看TestContextDecorator.enable和TestContextDecorator.disable函数,我们发现它们是在override_settings子类中实现的,那我们对于decorate_class的分析就到这里,enable和disable函数在后面分析到override_settings子类的时候会继续分析。
decorate_callable
注意: 这个函数的代码涉及到了Python的上下文管理器(
with语句)的部分知识,如果你可以自己动手写一个上下文管理器的话,继续阅读即可。 如果你对于上下文管理器还有些疑问的话,请参考我的另一篇文章: PEP 343: Python的with语句
分析完了decorate_class函数,我们接着回到TestContextDecorator.__call__方法,接着分析被修饰对象是可调用对象的那一种情况,也就是TestContextDecorator.decorate_callable函数。decorate_callable函数的代码如下所示:
def decorate_callable(self, func):
@wraps(func)
def inner(*args, **kwargs):
with self as context:
if self.kwarg_name:
kwargs[self.kwarg_name] = context
return func(*args, **kwargs)
return inner
可以看到,decorate_callable函数其实就是一个修饰器,它的主要功能就是将TestContextDecorator的实例作为一个上下文管理器进行了调用,需要注意的是,return语句是被包裹在with语句内的,也就是说,被修饰的函数执行完了以后,才会接着执行上下文管理器的__exit__函数。同时,它还会使用self.kwarg_name的值作为参数名字,将上下文管理器的返回值(也就是__enter__函数的返回值)传入被修饰的函数中。
既然decorate_callable将TestContextDecorator当做一个上下文管理器来使用,我们就需要关注一下它的__enter__和__exit__函数,这两个函数的代码如下所示:
def __enter__(self):
return self.enable()
def __exit__(self, exc_type, exc_value, traceback):
self.disable()
可以看到,这两个函数的功能,基本上也是调用enable和disable函数。
override_settings
分析完了override_settings的父类,我们接着来分析这个类本身。从上面的分析中我们可以看到,override_settings对于被修饰的类或者函数基本上就是做两件事,类或者函数初始化之前调用enable函数,并将enable函数的返回值传递到被修饰的类或者函数中。然后再在被修饰的类或者函数退出的时候调用disable函数。所以,我们将关注点集中到override_settings类的enable和disable函数上就好了。
首先来说override_settings.enable函数,它的代码如下所示:
def enable(self):
# Keep this code at the beginning to leave the settings unchanged
# in case it raises an exception because INSTALLED_APPS is invalid.
# 注意self.options其实就是`override_settings`类的构造函数中传入的关键字参数kwargs
if 'INSTALLED_APPS' in self.options:
try:
apps.set_installed_apps(self.options['INSTALLED_APPS'])
except Exception:
apps.unset_installed_apps()
raise
# 这个settings._wrapped就是真实保存的设置
# UserSettingsHolder就是
override = UserSettingsHolder(settings._wrapped)
for key, new_value in self.options.items():
setattr(override, key, new_value)
self.wrapped = settings._wrapped
settings._wrapped = override
for key, new_value in self.options.items():
setting_changed.send(sender=settings._wrapped.__class__,
setting=key, value=new_value, enter=True)
可以看到enable函数的功能就是在原有的settings配置上,用我们在options中传入的配置覆盖掉原来的配置,并针对修改的配置项发出信号。同时将原来的配置保存到self.wrapped变量中。
接着我们再来看override_settings.disable函数,它的代码如下:
def disable(self):
if 'INSTALLED_APPS' in self.options:
apps.unset_installed_apps()
settings._wrapped = self.wrapped
del self.wrapped
for key in self.options:
new_value = getattr(settings, key, None)
setting_changed.send(sender=settings._wrapped.__class__,
setting=key, value=new_value, enter=False)
这个函数的功能也很简单,将self.wrapped中保存的配置还原,并针对更改的配置发出信号。
总结
OK,至此,对于override_settings的分析就结束了,它的功能也比较简单,就是在一个作用域中修改配置,并在这个作用域结束的时候将配置还原,如果它修饰的作用域是一个函数的话,由于函数不像类有构造和析构函数,它就使用Python的上下文管理器来实现类的构造和析构函数的功能。