Horizon 源码阅读(二)—— Horizon 模块注册机制

2024-06-10 22:48

本文主要是介绍Horizon 源码阅读(二)—— Horizon 模块注册机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、写在前面

这篇文章主要介绍一下Openstack Horizon — juno项目模块注册机制,本文将通过对Horizon源码解析了解各个模块注册加载机制。全文穿插了Horizon组建的代码块以及个人理解注释,由于能力和时间有限,错误之处在所难免,欢迎指正!

       如果转载,请保留作者信息。
       邮箱地址:jpzhang.ht@gmail.com


二、Horizon模块注册机制

       Horizon/base.py中实现了一套dashboard/panel注册、动态加载机制,使得Horizon面板上所有的dashboard都是”可插拔”的,所有的panel都是”动态加载”的,在以下的内容里将通过源码解析具体分析horizon/base.py文件,揭开这神秘的面纱。

       流程图: 

       

       整个Horizon模块的加载流程如上图所示,horizon项目在引入的时候自动通过horizon.__init__()自动Load类属性方法def_lazy_urls(self),加载LazyURLPattern(url_patterns)其中url_patterns是一个嵌套方法,LazyURLPattern继承自Django SimpleLazyObject类,个人理解这是把_lazy_urls()方法转换成一个懒惰方法,这里只加载不执行,等正真用到的时候在执行。

       当用户通过Request请求访问的时候,根据Django Urls映射机制,会去openstack_dashboard.urls.py匹配,urls.py文件中url(r’', include(horizon.urls)),触发执行_lazy_urls()方法。_lazy_urls()方法返回def_urls(self)处理结果的第一个参数urlpatterns,整个Horizon模块的注册Urls编译都是在这个方法内完成,通过_autodiscover()方法,从“settings.INSTALLED_APPS发现模块,包含dashboard.py文件的模块进行注册,并添加到self._registry注册表中,然后通过循环遍历注册表,调用每个注册dashboard的_autodiscover()方法,注册每个dashboard下面的panel,完成整个horizon模块的注册,最终返回一个urlpatterns值,urls匹配调用相应的views模块。

       

 注意点:

          1、settings.INSTALLED_APPS在这里拉出来讲解一下,settings.INSTALLED_APPS是一个字符串tuple ,内容是项目中引入的应用,在Horizon项目中有两部分组成:

第一部分:settings.py

INSTALLED_APPS = ['openstack_dashboard',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_pyscss',
'openstack_dashboard.django_pyscss_fix',
'compressor',
'horizon',
'openstack_auth',]

第二部分:settings.py

# Load the pluggable dashboard settings
import openstack_dashboard.enabled
import openstack_dashboard.local.enabled
from openstack_dashboard.utils import settings
INSTALLED_APPS = list(INSTALLED_APPS)  # Make sure it's mutable
settings.update_dashboards([
openstack_dashboard.enabled,
openstack_dashboard.local.enabled,
], HORIZON_CONFIG, INSTALLED_APPS)

openstack_dashboard.enabled/openstack_dashboard.local.enabled:配置启用和显示Daishboard,用来配置Horizon左侧导航显示的dashboard。自己开发一个dashboard需要在openstack_dashboard/enabled 添加一个类似 _50_mydashboard.py文件,这样才会被注入到settings.INSTALLED_APPS配置项中。整个settings.INSTALLED_APPS中引入的APP由这两部分组拼在一起。

2、每个dashboard模块下都有一个dasbboard.py文件里面定义了属于当前dashboard的PanelGroup,和各个PanelGroup下的Panel,在Horizon模块被导入的时候会去依次遍历Dasboard→PanelGroup→Panel,所有的dashboard注册到Horizon命名空间下,各个panel注册到自己的dashboard命名空间下。

三、Horizon模块注册源码解析

       接下去刚通过分析Juno 中Horizon关于这部分的原码来讲解。

       细粒度的数据流图: 

       

       上图是关于Horizon中关于模块注册的数据流图。


1)horizon导入

      settings.py-> 导入horizon

      INSTALLED_APPS = [
                                       ‘horizon',

                                        ……
       ]

python中在导入一个包时,实际上导入了它的__init__.py文件,当我们导入Horizon这个包的时候,__init__.py文件自动运行,在__init__.py 文件中再导入其他的包,或者模块。其中在horizon包的__init__.py文件中:

    from horizon.baseimport Horizon  # noqa

    ……
    urls = Horizon._lazy_urls

    ……


导入了Horizon.base._lazy_urls方法,_lazy_urls()是完成整个模块注册的入口,Horizon.base._lazy_urls方法增加了@property, @property 可以将python定义的函数“当做”属性访问。__init__.py将Horizon.base._lazy_urls导入的时候自动执行该方法体。


注:horizon提供的Site类、Dashboard类、Panel类,负责整个基本架构,实现单例的设计模式,全局只有一个只有一个horizon对象。 对于这个文件代码架构的分析放到以后。

@property
def _lazy_urls(self):
"""
延迟加载URL模式。
这种方法避免试图加载URLconf值之前设置模块已经加载的问题。
"""
def url_patterns():
return self._urls()[0]
# LazyURLPattern(url_patterns) 猜测这是一个懒惰方法,load的时候把方法传入,当需要使用的使用才执行方法。
# request 请求进来的时候,回去Openstack.urls.py 进行配置,include('openstack_auth.urls')这个时候会执行url_patterns()
# self.namespace = "horizon"
# self.slug = "horizon"
return LazyURLPattern(url_patterns), self.namespace, self.slug

其中LazyURLPattern(url_patterns)没有继续执行下去,LazyURLPattern()继承自Djano SimpleLazyObject理解成将一个方法转换成延迟执行的方法,只有正真调用的时候才会正真执行,显然这里没有正真调用,这里只是一个加载,url_patterns作为一个方法引用当作参数。

     

2)ruquest请求

      当用户通过浏览器发起request请求,根据Django的框架结构,使用URLconf进行连接请求和后端处理(view)的绑定,使用view进行后端处理,使用template进行页面渲染。

      

urlpatterns = patterns(
'',
# 匹配网站根目录的URL,映射到openstack_dashboard.views.splash视图。
url(r'^$', 'openstack_dashboard.views.splash', name='splash'),
# 任何以/auth/开头的URL将会匹配,引入openstack_auth.urls
url(r'^auth/', include('openstack_auth.urls')),
# 任何以/api/开头的URL将会匹配,引入openstack_dashboard.api.rest.urls
url(r'^api/', include('openstack_dashboard.api.rest.urls')),
# 任何访问URL将会匹配,都引用horizon.urls
url(r'', include(horizon.urls)),
)

       openstack_dashboard.urls.py:

       url(r'', include(horizon.urls)): horizon.urls对应的是horizon.baise._lazy_urls(),执行之前导入horizon包加载_lazy_urls()方法中的

            def url_patterns():
                 
return self._urls()[0]


接着调用 horizon.base.Site._urls(),完成所有Dashboard和Panel的注册变编译URLconf:

def _urls(self):
"""
从注册的Dashboards构造Horizon的Urlconf
"""
# 获取horizon.site_urls urlpatterns 值
urlpatterns = self._get_default_urlpatterns()
# 从“settings.INSTALLED_APPS发现模块,包含dashboard.py、panel.py的模块注册,添加到注册表中self._registry,没有的抛出异常。
self._autodiscover()--------------------标注(1)
# 发现每个Dashboards里的Panels
# 从注册表self._registry 取出注册dashboard,注册每个dashboard中的panel
# self._registry:{: ,
# : ,
# : , # : } #self._registry.values():[ , , , ] for dash in self._registry.values(): dash._autodiscover() -----------------标注(2) # 加载基于插件的面板配置 self._load_panel_customization() # 允许覆盖模块 if self._conf.get("customization_module", None): customization_module = self._conf["customization_module"] bits = customization_module.split('.') mod_name = bits.pop() package = '.'.join(bits) mod = import_module(package) try: before_import_registry = copy.copy(self._registry) import_module('%s.%s' % (package, mod_name)) except Exception: self._registry = before_import_registry if module_has_submodule(mod, mod_name): raise # 编译动态URL配置。 for dash in self._registry.values(): urlpatterns += patterns('', url(r'^%s/' % dash.slug, include(dash._decorated_urls))) # 返回三个参数,django.conf.urls.include “"" 返回的urlpattern参数值 ([ , \S+?)/$>, , (None:None) ^i18n/>, (settings:settings) ^settings/>, (identity:identity) ^identity/>, (project:project) ^project/>, (admin:admin) ^admin/>, (settings:settings) ^settings/>, (identity:identity) ^identity/>, (project:project) ^project/>, (admin:admin) ^admin/>], 'horizon', 'horizon') """ return urlpatterns, self.namespace, self.slug 

标记(1)从“settings.INSTALLED_APPS发现模块,包含dashboard.py、panel.py的模块注册,添加到注册表中self._registry,没有的抛出异常。

                horizon.Site._autodiscover():

def _autodiscover(self):
"""
从“settings.INSTALLED_APPS发现模块注册的
确保适当的模块引入并注册到horizon中
self = """
# 判断self对象中是否存在_registerable_class 如果没有抛出异常,
# 你必须设置一个“registerable_class”属性以使用自动发现。
# self._registerable_class = 这在Class Site中已经设定了。
if not getattr(self, '_registerable_class', None):
raise ImproperlyConfigured('You must set a '
'"_registerable_class" property '
'in order to use autodiscovery.')
'''
循化settings.INSTALLED_APPS,导入每个APP中的dashboard、panel模块,即每个模块下面的dashboard.py 和 panel.py
settings.INSTALLED_APPS => ['openstack_dashboard.dashboards.project',
'openstack_dashboard.dashboards.admin',
'openstack_dashboard.dashboards.identity',
'openstack_dashboard.dashboards.settings',
'openstack_dashboard',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_pyscss',
'openstack_dashboard.django_pyscss_fix',
'compressor',
'horizon',
'openstack_auth']
'''
for mod_name in ('dashboard', 'panel'):
for app in settings.INSTALLED_APPS:
mod = import_module(app)
try:
'''
注册表 self._registry = {}
定义:class Registry(object):
def __init__(self):
self._registry = {}
......
copy.copy():copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。
'''
before_import_registry = copy.copy(self._registry)
"""
import_module() 执行这个模块
例如:app:'openstack_dashboard.dashboards.project'
mod_name:'dashboard'
import_module('%s.%s' % (app, mod_name)) =>  执行openstack_dashboard.dashboards.project.dashboard.py => horizon.register(Project)
"""
import_module('%s.%s' % (app, mod_name))
except Exception:
# 如果APP中没有dashboard抛出异常
self._registry = before_import_registry
if module_has_submodule(mod, mod_name):
raise

标记(2)发现每个Dashboards里的Panels,从注册表self._registry 取出注册dashboard,注册每个dashboard中的panel

                horizon.Dashboard_autodiscover():

def _autodiscover(self):
"""      
从当前的dashboard模块中发现panel并注册
"""
# self._autodiscover_complete:True 自动完成发现否时完成的一个标记
if getattr(self, "_autodiscover_complete", False):
return
# self._autodiscover_complete:False
panels_to_discover = []
panel_groups = []
# all(iterable):如果iterable的所有元素不为0、''、False或者iterable为空,all(iterable)返回True,否则返回False;
# basestring:basestring是str和unicode的超类(父类),也是抽象类,因此不能被调用和实例化,
# 但可以被用来判断一个对象是否为str或者unicode的实例,isinstance(obj, basestring)等价于isinstance(obj, (str, unicode));
# isinstance: 判断一个对象是否是一个已知的类型
#
#例如: self:#      self.panels:('domains', 'projects', 'users', 'groups', 'roles')
if all([isinstance(i, basestring) for i in self.panels]):
self.panels = [self.panels]
# 现在迭代设置panel
# self.panels: [('domains', 'projects', 'users', 'groups', 'roles')]
for panel_set in self.panels:
# 实例化PanelGroup类。
# panel_set:('domains', 'projects', 'users', 'groups', 'roles')
if not isinstance(panel_set, collections.Iterable) and \
issubclass(panel_set, PanelGroup):# 返回错误,panel_set不是一个class  TypeError('issubclass() arg 1 must be a class',)
panel_group = panel_set(self)
# 检查嵌套的元组,并将其转换成PanelGroups
elif not isinstance(panel_set, PanelGroup):
# 返回一个对象
panel_group = PanelGroup(self, panels=panel_set)
# 存放返回结果
# 例如:panel_group.panels:['domains', 'projects', 'users', 'groups', 'roles']
panels_to_discover.extend(panel_group.panels)
panel_groups.append((panel_group.slug, panel_group))
# self._panel_groups:{'default': }
self._panel_groups = SortedDict(panel_groups)
# 加载panel_groups中的每一个panel
# package:'openstack_dashboard.dashboards.settings'
package = '.'.join(self.__module__.split('.')[:-1])
mod = import_module(package)
for panel in panels_to_discover:
try:
before_import_registry = copy.copy(self._registry)
import_module('.%s.panel' % panel, package)
except Exception:
self._registry = before_import_registry
if module_has_submodule(mod, panel):
raise
# 标记自动注册Panel已经完成
self._autodiscover_complete = True

这篇关于Horizon 源码阅读(二)—— Horizon 模块注册机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1049492

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分