Day 1:从 Superset 启动和 init_views 入手

本文是基于 Superset 5.0 源码阅读过程整理的学习笔记。
Day 1 主要目标:先搞清楚 Superset 的启动入口,以及 API、页面、菜单是如何被注册到系统中的。


1. Superset 本质是 Flask 应用

Superset 的后端本质上是一个 Flask 应用。

它的启动入口大致是:

create_app()

启动时会创建 Flask app,加载配置,然后调用初始化器:

SupersetAppInitializer(app).init_app()

真正大量初始化逻辑在:

superset/initialization/__init__.py

初始化过程大致包括:

加载配置
初始化日志
初始化 Feature Flag
初始化数据库
初始化 Celery
初始化缓存
初始化 Flask-AppBuilder
注册 API / View / Menu

其中二次开发最值得先看的方法是:

init_views()

不过需要注意:

init_views() 不是 Superset 的全部启动流程。
它只负责 API、页面 View、菜单入口的注册。
数据库、缓存、Celery、权限管理器等初始化在 init_app() 的其他步骤中完成。

也就是说,init_views() 是理解 Superset 后端模块注册的入口,但不是整个系统启动的全部逻辑。


2. init_views 是 API / 页面 / 菜单注册中心

init_views() 主要负责把 Superset 的 API、页面 View、菜单入口注册到 Flask-AppBuilder 中。

常见注册方式有四种:

appbuilder.add_api(...)
appbuilder.add_view(...)
appbuilder.add_link(...)
appbuilder.add_view_no_menu(...)

含义如下:

方法作用
add_api注册 REST API
add_view注册页面 View,并可能加入菜单
add_link注册菜单链接
add_view_no_menu注册页面,但不显示在菜单中

例如:

appbuilder.add_api(DatabaseRestApi)
appbuilder.add_api(DashboardRestApi)
appbuilder.add_api(ChartRestApi)
appbuilder.add_api(SqlLabRestApi)

这些是注册数据库、仪表盘、图表、SQL Lab 相关 API。

再比如:

appbuilder.add_view(
    DatabaseView,
    "Databases",
    label=__("Database Connections"),
    icon="fa-database",
    category="Data",
    category_label=__("Data"),
)

这表示注册数据库连接管理页面,对应页面入口大致是:

Settings -> Data -> Database Connections

所以可以把 init_views() 理解为:

Superset 后端 API、页面、菜单的集中注册位置。

3. add_api、add_view、add_link、add_view_no_menu 的区别

3.1 add_api

add_api 用来注册 REST API。

例如:

appbuilder.add_api(DatabaseRestApi)

注册后,前端就可以通过类似下面的接口访问数据库连接相关 API:

/api/v1/database/

常见 API 有:

DatabaseRestApi
DatasetRestApi
ChartRestApi
DashboardRestApi
SqlLabRestApi
SecurityRestApi

这些 API 主要负责处理前端发来的 REST 请求,比如查询列表、新增、修改、删除、执行 SQL 等。


3.2 add_view

add_view 用来注册页面 View,并可能把它挂到菜单中。

例如:

appbuilder.add_view(
    DatabaseView,
    "Databases",
    label=__("Database Connections"),
    icon="fa-database",
    category="Data",
    category_label=__("Data"),
)

这会注册一个数据库连接管理页面。

页面本身通常只是返回 Superset 的前端 React 页面壳子,真正的数据获取还是通过 REST API 完成。

例如:

打开 /databaseview/list/
  ↓
返回 React 页面
  ↓
前端再请求 /api/v1/database/ 获取数据库连接列表

3.3 add_link

add_link 用来注册菜单链接。

例如:

appbuilder.add_link(
    "SQL Editor",
    label=__("SQL Lab"),
    href="/sqllab/",
    category="SQL Lab",
    category_label=__("SQL"),
)

它不会像 add_view 那样注册一个完整 View 类,而是在菜单中增加一个跳转入口。

点击后会跳到:

/sqllab/

3.4 add_view_no_menu

add_view_no_menu 用来注册页面,但不显示在菜单中。

例如:

appbuilder.add_view_no_menu(ExploreView)
appbuilder.add_view_no_menu(SqllabView)
appbuilder.add_view_no_menu(Superset)

这些页面可以被访问,但不会直接出现在菜单里。

比如 Explore 页面通常是从 Chart 跳过去的,不一定需要一个单独菜单入口。

可以理解为:

add_view:
    注册页面,并可能显示到菜单

add_view_no_menu:
    注册页面,但不显示菜单

4. DatabaseView、Databases、Database Connections 的区别

以这段代码为例:

appbuilder.add_view(
    DatabaseView,
    "Databases",
    label=__("Database Connections"),
    icon="fa-database",
    category="Data",
    category_label=__("Data"),
)

里面有几个名字很容易混:

名称含义
DatabaseViewPython 类名
"Databases"FAB 内部注册名
"Database Connections"菜单展示名称
category="Data"菜单分组
class_permission_name = "Database"权限系统里的权限对象名

所以可以这样理解:

DatabaseView 是代码里的类
Databases 是 FAB 注册名
Database Connections 是用户看到的菜单名
Database 是权限系统里的名字

这几个名字虽然相关,但作用不同。


5. URL 是怎么生成的

以 DatabaseView 为例:

class DatabaseView(BaseSupersetView):
    @expose("/list/")
    @has_access
    def list(self):
        return super().render_app_template()

如果一个 View 没有显式设置 route_base,Flask-AppBuilder 默认会根据类名生成 URL 前缀:

DatabaseView -> databaseview

再拼上方法上的路由:

@expose("/list/")

最终得到:

/databaseview/list/

所以 Database Connections 页面一般对应:

/databaseview/list/

这里需要注意,URL 不是由:

"Databases"

或者:

label="Database Connections"

生成的,而是主要由:

View 类名
+
@expose 路径

决定的。


6. route_base 的作用

如果某个 View 类中显式定义了 route_base,则会优先使用 route_base。

例如:

class TagModelView(SupersetModelView):
    route_base = "/superset/tags"

那么它的访问路径就会基于:

/superset/tags

而不是默认的:

/tagmodelview

所以 URL 生成规则可以总结为:

默认情况:
    类名小写 + @expose 路径

自定义情况:
    route_base + @expose 路径

7. DatabaseView 页面本身做了什么

DatabaseView 类大概类似:

class DatabaseView(BaseSupersetView):
    class_permission_name = "Database"
    method_permission_name = MODEL_VIEW_RW_METHOD_PERMISSION_MAP

    @expose("/list/")
    @has_access
    def list(self):
        return super().render_app_template()

这段代码的含义是:

class_permission_name = "Database"
    表示这个 View 在权限系统里的权限对象名是 Database

method_permission_name = MODEL_VIEW_RW_METHOD_PERMISSION_MAP
    表示把 list/show/add/edit/delete 等方法映射成 read/write 权限

@expose("/list/")
    注册 URL 路由

@has_access
    访问前检查当前用户是否有权限

render_app_template()
    返回 Superset 前端 React 页面

也就是说,DatabaseView.list() 本身不一定直接查询数据库连接列表。

它主要负责:

注册页面路由
做权限校验
返回前端页面模板

真正的数据获取通常由 REST API 完成,比如:

/api/v1/database/

8. 页面 View 和 REST API 的关系

Superset 很多页面是这样的模式:

View 返回前端页面
  ↓
React 页面加载
  ↓
前端请求 REST API 获取数据

以 Database 为例:

/databaseview/list/
  ↓
DatabaseView.list()
  ↓
返回 React 页面
  ↓
前端请求 /api/v1/database/
  ↓
DatabaseRestApi 返回数据库连接列表

所以二次开发时,要区分:

页面入口:
    DatabaseView

数据接口:
    DatabaseRestApi

类似地:

页面入口数据接口
DatabaseViewDatabaseRestApi
DashboardModelView / DashboardDashboardRestApi
SliceModelViewChartRestApi
TableModelView / Datasets linkDatasetRestApi
SqllabViewSqlLabRestApi

9. init_views 中值得重点看的模块

init_views() 里注册了很多模块。

二次开发时不建议一开始全部看完,可以按优先级学习。

第一优先级

DatabaseRestApi
DatasetRestApi
ChartRestApi
DashboardRestApi
SqlLabRestApi

这些是 Superset 最核心的业务模块。

第二优先级

SecurityRestApi
CurrentUserRestApi
UserRestApi
RLSRestApi
SavedQueryRestApi

这些和用户、权限、SQL 查询、行级权限相关。

第三优先级

TagRestApi
AnnotationRestApi
ReportScheduleRestApi
EmbeddedDashboardRestApi
ImportExportRestApi
LogRestApi

这些是标签、注释、报表、嵌入式看板、导入导出、日志相关功能。


10. 看一个 API 模块时应该怎么看

以:

appbuilder.add_api(DatabaseRestApi)

为例,下一步应该去看:

superset/databases/api.py

重点看这些内容:

resource_name 是什么
class_permission_name 是什么
method_permission_name 是什么
base_permissions 有哪些
@expose 注册了哪些 URL
@protect / @permission_name 控制什么权限
调用了哪些 Command
调用了哪些 DAO
操作了哪些 Model

通常一个模块的后端结构是:

API 层
  ↓
Command 层
  ↓
DAO 层
  ↓
Model 层
  ↓
Metadata DB

例如 Database 模块大致是:

DatabaseRestApi
  ↓
CreateDatabaseCommand / UpdateDatabaseCommand / DeleteDatabaseCommand
  ↓
DatabaseDAO
  ↓
Database Model

Chart 模块大致是:

ChartRestApi
  ↓
CreateChartCommand / UpdateChartCommand
  ↓
ChartDAO
  ↓
Slice Model

11. Day 1 小结

这一部分主要解决了一个问题:

Superset 的 API、页面和菜单是怎么挂载到系统里的?

整体链路可以理解为:

Superset 启动
  ↓
SupersetAppInitializer.init_app()
  ↓
init_views()
  ↓
appbuilder.add_api / add_view / add_link / add_view_no_menu
  ↓
API、页面、菜单被注册到 FAB
  ↓
后续由 FAB 权限体系决定用户能否看到和访问

init_views() 是阅读 Superset 二次开发源码的一个很好入口,因为它集中列出了 Superset 的主要模块:

Database
Dataset
Chart
Dashboard
SQL Lab
Security
Annotation
Tag
Report
RLS

后续学习时,可以从这里挑一个模块继续往下追,比如:

DatabaseRestApi
  ↓
Command
  ↓
DAO
  ↓
Model

12. Day 1 最终理解

经过 Day 1 的源码阅读,可以先建立这样一个认知:

Superset 是 Flask 应用
  ↓
使用 Flask-AppBuilder 组织 View、API、菜单和权限
  ↓
init_views 是 API / 页面 / 菜单注册入口
  ↓
add_api 注册 REST API
  ↓
add_view 注册页面和菜单
  ↓
add_link 注册菜单链接
  ↓
add_view_no_menu 注册隐藏页面
  ↓
页面 View 通常返回前端 React 壳子
  ↓
真正数据由 REST API 提供

掌握这部分之后,再继续看权限体系、当前用户登录态、SQL Lab 执行 SQL,会顺很多。

更多推荐