文章概要:
1.requires和provider装饰器的作用
2.requires和provider装饰器实现原理
在keystone中可以经常看到controllers.py文件下有如下装饰器的使用:
@dependency.requires(‘name’)
@dependency.requires('assignment_api', 'catalog_api', 'identity_api',
'resource_api', 'token_provider_api', 'trust_api')
class Auth(controller.V3Controller):
# Note(atiwari): From V3 auth controller code we are
# calling protection() wrappers, so we need to setup
# the member_name and collection_name attributes of
# auth controller code.
# In the absence of these attributes, default 'entity'
# string will be used to represent the target which is
# generic. Policy can be defined using 'entity' but it
# would not reflect the exact entity that is in context.
# We are defining collection_name = 'tokens' and
# member_name = 'token' to facilitate policy decisions.
collection_name = 'tokens'
member_name = 'token'
def __init__(self, *args, **kw):
super(Auth, self).__init__(*args, **kw)
keystone.conf.auth.setup_authentication()
self._mfa_rules_validator = core.UserMFARulesValidator()
@dependency.provider('name')
@dependency.provider('id_mapping_api')
class MappingManager(manager.Manager):
"""Default pivot point for the ID Mapping backend."""
driver_namespace = 'keystone.identity.id_mapping'
def __init__(self):
super(MappingManager, self).__init__(CONF.identity_mapping.driver)
先举个例子看下用法:
# -*- coding:utf-8 -*-
import dependency
@dependency.provider('OBJ_A')
class A(object):
def __init__(self):
self.a = 1
def func_A(self):
print "func_A"
@dependency.requires('OBJ_A')
class C(object):
def __init__(self):
self.c = 3
def func_C(self):
print "func_C"
def test(self):
self.OBJ_A.func_A()
if __name__ == "__main__":
obj_a = A()
obj_c = C()
obj_c.test() # 输出:func_A
这样做的好处是可以实现模块间的解耦,例如上述例子class A提供了功能func_A,class C可以通过装饰器requires修饰后调用class A中的func_A方法,两个模块间互不干依赖
看下如下例子:
@dependency.requires('OBJ_C')
@dependency.provider('OBJ_A')
class A(object):
def __init__(self):
self.a = 1
def func_A(self):
print "func_A"
def test_A(self):
self.OBJ_C.func_C()
@dependency.requires('OBJ_A')
@dependency.provider('OBJ_C')
class C(object):
def __init__(self):
self.c = 3
def func_C(self):
print "func_C"
def test_C(self):
self.OBJ_A.func_A()
if __name__ == "__main__":
obj_a = A()
obj_c = C()
obj_a.test_A() # 输出:func_C
obj_c.test_C() # 输出:func_A
我们可以看到A依赖C,C也依赖A,其实这里是利用了依赖注入技术中的构造注入方法,通过创建类的实例过程中实现依赖关系的解决,通过看下面的源码解析可以看到,核心思想就是替换类的__init__方法为新的方法,该方法中做了两件事:1.调用__init__方法 2.依赖关系的记录或依赖关系的解决
先看下主要的三个字典:
# 存放了key为provider_name,value为provider实例
# 即provider_name为装饰器provider(name)中的那么,
# value为provider装饰器修饰的类实例
_REGISTRY = {}
# 存放类key为provider_name,value为依赖该provider的对象
# 即value为装饰器requires修饰的类
_future_dependencies = {}
# 存放key为provider_name,value为被装饰器provider装饰的类
# 例如:_factories: {'OBJ_C': class C}
_factories = {}
requires源码解析额
查看requires源码:
def requires(*dependencies):
# 该方法在类初始化时调用,也就是调用__init__之后再调用_process_dependencies
def wrapper(self, *args, **kwargs):
"""Inject each dependency from the registry."""
self.__wrapped_init__(*args, **kwargs)
_process_dependencies(self)
def wrapped(cls):
"""Note the required dependencies on the object for later injection.
The dependencies of the parent class are combined with that of the
child class to create a new set of dependencies.
"""
# 查看类有没有_dependencies属性,有则取出值,没有则为该类设置该属性
# 该属性的值存放的是依赖项,即装饰器@requires(name)中的name
existing_dependencies = getattr(cls, '_dependencies', set())
cls._dependencies = existing_dependencies.union(dependencies)
# 这里主要是将__init__方法替换成wrapper方法,
# 其实最终要达到的效果就是调用类初始化__init__方法之后再调用_process_dependencies方法
if not hasattr(cls, '__wrapped_init__'):
cls.__wrapped_init__ = cls.__init__
cls.__init__ = wrapper
return cls
return wrapped
_process_dependencies方法作用:
1.设置依赖属性
2.记录依赖关系
def _process_dependencies(obj):
# 该函数的作用是解决对象的依赖关系,即
# 1.如果依赖项(也就是装饰器requires(name)中的name)已经存在即实例化,则直接将其设置为obj的属性
# 这样就可以达到调用依赖项中方法的效果
# 2.如果依赖项没有注册(即未实例化),则将该依赖关系记录到
# 一个字典中,该字典中记录的就是那些还没有将依赖项作为属性设置的obj
# 如果后面依赖项实例化,则可以使用该字典记录的依赖关系为这些obj设置依赖项属性
def process(obj, attr_name, unresolved_in_out):
# 获取对象需要的依赖项
for dependency in getattr(obj, attr_name, []):
# 如果依赖项不在_REGISTRY字典中
if dependency not in _REGISTRY:
# We don't know about this dependency, so save it for later.
#
# 则将依赖关系记录进_future_dependencies字典中
# key为依赖项的name,value为依赖name的实例
# 后面provider装饰器会使用resolve_future_dependencies方法解决该依赖关系
unresolved_in_out.setdefault(dependency, []).append(obj)
continue
# 如果依赖项已经存在,则直接将该依赖项设置为该对象的属性
setattr(obj, dependency, get_provider(dependency))
process(obj, '_dependencies', _future_dependencies)
provider源码解析
查看provider源码:
def provider(name):
def wrapper(cls):
# 将__init__方法替换成该方法
def wrapped(init):
def __wrapped_init__(self, *args, **kwargs):
"""Initialize the wrapped object and add it to the registry."""
# 类初始化,执行__init__方法
init(self, *args, **kwargs)
# 将name为key,self(对象实例)为valuep注册进_REGISTRY中
_set_provider(name, self)
# 注意这里,该方法是用来解决依赖关系的
# 我们通过看require装饰器源码可以看到,对于由于依赖项未注册导致
# 有些对象没有设置属性的,该方法就是为那些没有设置name属性的对象设置属性
resolve_future_dependencies(__provider_name=name)
return __wrapped_init__
cls.__init__ = wrapped(cls.__init__)
_factories[name] = cls
return cls
return wrapper
def resolve_future_dependencies(__provider_name=None):
new_providers = dict()
if __provider_name:
# A provider was registered, so take care of any objects depending on
# it.
# 获取依赖name的对象
targets = _future_dependencies.pop(__provider_name, [])
for target in targets:
# 为那些没有设置name属性的对象设置属性
setattr(target, __provider_name, get_provider(__provider_name))
return
# Resolve future dependencies, raises UnresolvableDependencyException if
# there's no provider registered.
# 这里是一次性解决所有依赖关系的
try:
for dependency, targets in _future_dependencies.copy().items():
if dependency not in _REGISTRY:
# a Class was registered that could fulfill the dependency, but
# it has not yet been initialized.
factory = _factories.get(dependency)
if factory:
provider = factory()
new_providers[dependency] = provider
else:
raise UnresolvableDependencyException(dependency, targets)
for target in targets:
setattr(target, dependency, get_provider(dependency))
finally:
# 解决完所有依赖后清楚_future_dependencies
_future_dependencies.clear()
return new_providers
完整源码如下:
# Copyright 2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""This module provides support for dependency injection.
Providers are registered via the ``@provider()`` decorator, and dependencies on
them are registered with ``@requires()``. Providers are available to their
consumers via an attribute. See the documentation for the individual functions
for more detail.
See also:
https://en.wikipedia.org/wiki/Dependency_injection
"""
import traceback
from keystone.i18n import _
_REGISTRY = {}
_future_dependencies = {}
_factories = {}
def _set_provider(name, provider):
_original_provider, where_registered = _REGISTRY.get(name, (None, None))
if where_registered:
raise Exception('%s already has a registered provider, at\n%s' %
(name, ''.join(where_registered)))
_REGISTRY[name] = (provider, traceback.format_stack())
GET_REQUIRED = object()
GET_OPTIONAL = object()
def get_provider(name, optional=GET_REQUIRED):
if optional is GET_REQUIRED:
return _REGISTRY[name][0]
return _REGISTRY.get(name, (None, None))[0]
class UnresolvableDependencyException(Exception):
"""Raised when a required dependency is not resolvable.
See ``resolve_future_dependencies()`` for more details.
"""
def __init__(self, name, targets):
msg = _('Unregistered dependency: %(name)s for %(targets)s') % {
'name': name, 'targets': targets}
super(UnresolvableDependencyException, self).__init__(msg)
def provider(name):
"""A class decorator used to register providers.
When ``@provider()`` is used to decorate a class, members of that class
will register themselves as providers for the named dependency. As an
example, In the code fragment::
@dependency.provider('foo_api')
class Foo:
def __init__(self):
...
...
foo = Foo()
The object ``foo`` will be registered as a provider for ``foo_api``. No
more than one such instance should be created; additional instances will
replace the previous ones, possibly resulting in different instances being
used by different consumers.
"""
def wrapper(cls):
def wrapped(init):
def __wrapped_init__(self, *args, **kwargs):
"""Initialize the wrapped object and add it to the registry."""
init(self, *args, **kwargs)
_set_provider(name, self)
resolve_future_dependencies(__provider_name=name)
return __wrapped_init__
cls.__init__ = wrapped(cls.__init__)
_factories[name] = cls
return cls
return wrapper
def _process_dependencies(obj):
# Any dependencies that can be resolved immediately are resolved.
# Dependencies that cannot be resolved immediately are stored for
# resolution in resolve_future_dependencies.
def process(obj, attr_name, unresolved_in_out):
for dependency in getattr(obj, attr_name, []):
if dependency not in _REGISTRY:
# We don't know about this dependency, so save it for later.
unresolved_in_out.setdefault(dependency, []).append(obj)
continue
setattr(obj, dependency, get_provider(dependency))
process(obj, '_dependencies', _future_dependencies)
def requires(*dependencies):
"""A class decorator used to inject providers into consumers.
The required providers will be made available to instances of the decorated
class via an attribute with the same name as the provider. For example, in
the code fragment::
@dependency.requires('foo_api', 'bar_api')
class FooBarClient:
def __init__(self):
...
...
client = FooBarClient()
The object ``client`` will have attributes named ``foo_api`` and
``bar_api``, which are instances of the named providers.
Objects must not rely on the existence of these attributes until after
``resolve_future_dependencies()`` has been called; they may not exist
beforehand.
Dependencies registered via ``@required()`` must have providers; if not,
an ``UnresolvableDependencyException`` will be raised when
``resolve_future_dependencies()`` is called.
"""
def wrapper(self, *args, **kwargs):
"""Inject each dependency from the registry."""
self.__wrapped_init__(*args, **kwargs)
_process_dependencies(self)
def wrapped(cls):
"""Note the required dependencies on the object for later injection.
The dependencies of the parent class are combined with that of the
child class to create a new set of dependencies.
"""
existing_dependencies = getattr(cls, '_dependencies', set())
cls._dependencies = existing_dependencies.union(dependencies)
if not hasattr(cls, '__wrapped_init__'):
cls.__wrapped_init__ = cls.__init__
cls.__init__ = wrapper
return cls
return wrapped
def resolve_future_dependencies(__provider_name=None):
"""Force injection of all dependencies.
Before this function is called, circular dependencies may not have been
injected. This function should be called only once, after all global
providers are registered. If an object needs to be created after this
call, it must not have circular dependencies.
If any required dependencies are unresolvable, this function will raise an
``UnresolvableDependencyException``.
Outside of this module, this function should be called with no arguments;
the optional argument, ``__provider_name`` is used internally, and should
be treated as an implementation detail.
"""
new_providers = dict()
if __provider_name:
# A provider was registered, so take care of any objects depending on
# it.
targets = _future_dependencies.pop(__provider_name, [])
for target in targets:
setattr(target, __provider_name, get_provider(__provider_name))
return
# Resolve future dependencies, raises UnresolvableDependencyException if
# there's no provider registered.
try:
for dependency, targets in _future_dependencies.copy().items():
if dependency not in _REGISTRY:
# a Class was registered that could fulfill the dependency, but
# it has not yet been initialized.
factory = _factories.get(dependency)
if factory:
provider = factory()
new_providers[dependency] = provider
else:
raise UnresolvableDependencyException(dependency, targets)
for target in targets:
setattr(target, dependency, get_provider(dependency))
finally:
_future_dependencies.clear()
return new_providers
def reset():
"""Reset the registry of providers.
This is useful for unit testing to ensure that tests don't use providers
from previous tests.
"""
_REGISTRY.clear()
_future_dependencies.clear()