- 后端
- Web框架
【免费下载链接】bottle
bottle.py is a fast and simple micro-framework for python web-applications.
Bottle 是一个快速、简洁的 Python 微框架,官方文档维护了一份持续增长的第三方插件清单(docs/plugins/list.rst),用于为框架补充核心之外的能力、集成第三方库或自动化重复劳动。本文以这份官方清单为主线,完整收录其中收录的 24 个第三方插件及其用途,并结合 使用插件指南、插件开发指南 与 bottle.py 源码,深入讲解插件的安装、卸载、按路由应用/跳过、配置方式以及底层实现原理。读完本文,你将能够对照需求挑选合适的第三方插件,熟练管理应用级与路由级插件,并理解乃至亲手编写一个 Bottle 插件。
一、Bottle 插件生态:为什么需要第三方插件
Bottle 的核心特性覆盖了大多数常见用例,但作为微框架,它有自己的边界。这正是"插件(Plugins)"登场的场景:插件为框架补充缺失的功能、集成第三方库,或者只是自动化一些重复性的工作(见 docs/plugins/index.rst)。
官方维护的插件清单(docs/plugins/list.rst)收录了由第三方开发和维护的插件。这些插件大多被设计为可移植、可在多个应用间复用的组件——也许你的问题已经被解决,现成的插件即可直接使用;如果没有,也可以参考 插件开发指南 自己编写一个。
需要特别说明的是:本清单中列出的插件不属于 Bottle 项目本身,而是由第三方开发与维护。官方仅负责在文档中收录与推介,插件的具体行为、兼容性与维护状态请以各自项目的发布页面为准。
二、第三方插件总览:官方收录的 24 个插件清单
下面按功能领域归类整理官方清单中的全部插件,每个插件均保留原始文档中的一句话描述。
会话与缓存
- Bottle-Beaker:以 WSGI 中间件方式集成 Beaker 会话与缓存库。
- canister:一个 Bottle 封装(wrapper),提供日志(logging)、会话(sessions)与认证(authentication)能力。
认证与授权
- Bottle-Cork:提供一组简单的方法,在基于 Bottle 的 Web 应用中实现认证(Authentication)与授权(Authorization)。
- bottle-jwt:bottle.py 的 JSON Web Token(JWT)认证插件。
- bottlejwt:Bottle 的 JWT 集成。
- canister:如上所述,同时覆盖认证场景。
跨域(CORS)
- Bottle-Cors-plugin:在 Bottle Web 应用中实现 CORS 的最简单方式。
辅助元包
- Bottle-Extras:元包(Meta package),用于一次性安装整个 Bottle 插件集合。
闪现消息(Flash)
- Bottle-Flash:Bottle 的 flash 消息插件,用于在请求间传递一次性提示信息。
队列
- Bottle-Hotqueue:构建于 Redis 之上的 Bottle FIFO 队列。
数据库与 ORM
- Macaron:一个面向 SQLite 的对象关系映射器(ORM)。
- Bottle-Memcache:Bottle 的 Memcache 集成。
- Bottle-Mongo:Bottle 的 MongoDB 集成。
- Bottle-Redis:Bottle 的 Redis 集成。
- Bottle-Sqlalchemy:Bottle 的 SQLAlchemy 集成。
- Bottle-Sqlite:Bottle 的 SQLite3 数据库集成。
- Bottle-Web2pydal:Bottle 的 Web2py Dal 集成。
OAuth2.0
- Bottle-OAuthlib:oauthlib 的适配器,用于创建你自己的 OAuth2.0 实现。
模板渲染
- Bottle-Renderer:Bottle 的渲染器插件。
静态文件服务
- Bottle-Servefiles:一个可复用的应用,为 Bottle 应用提供静态文件服务。
WSGI / 请求与响应对象增强
- Bottle-Werkzeug:集成
werkzeug库(提供替代的 request/response 对象、高级调试中间件等)。
查询参数解析与数据校验
- bottle-smart-filters:Bottle 查询字符串(querystring)的智能猜测。
- bottle-cerberus:Bottle 的 Cerberus 集成(数据校验)。
错误处理
- Bottle-errorsrest:将 Bottle 产生的所有错误以 JSON 形式返回。
参数自动注入
- Bottle-tools:使用 POST / query string 数据自动为函数提供参数的装饰器集合。
如何按需挑选
官方清单中的插件覆盖了 Web 开发中最常见的几大痛点,可以根据需求快速定位:
| 你的需求 | 可参考的插件 |
|---|---|
| 会话、缓存 | Bottle-Beaker、canister |
| 登录与权限控制 | Bottle-Cork、bottle-jwt、bottlejwt |
| 跨域资源共享 | Bottle-Cors-plugin |
| 数据库访问 | Bottle-Sqlite、Bottle-Sqlalchemy、Bottle-Mongo、Macaron、Bottle-Redis、Bottle-Memcache |
| 用户提示消息 | Bottle-Flash |
| JSON 错误响应 | Bottle-errorsrest |
| 请求参数校验/转换 | bottle-cerberus、bottle-smart-filters、Bottle-tools |
| 静态资源托管 | Bottle-Servefiles |
这些插件大多发布在 PyPI 等包仓库,可通过pip等包管理器安装(具体安装方式与用法请参考各自项目的文档)。安装后,它们遵循 Bottle 统一的插件管理机制——这正是下一节的内容。
三、插件的安装、卸载与按路由应用
Bottle 的插件系统建立在 Python 装饰器(decorator)的概念之上。简而言之,插件就是一个被应用到应用中所有路由回调上的装饰器。插件能做到的远不止装饰路由回调,但理解这一点是理解整个体系的最佳起点。
3.1 一个最小的装饰器插件:stopwatch
以下示例(源自 docs/plugins/index.rst)度量被包裹函数的执行时间,并把结果写入非标准的X-Exec-Time响应头:
import time from bottle import response def stopwatch(callback): def wrapper(*args, **kwargs): start = time.time() result = callback(*args, **kwargs) end = time.time() response.headers['X-Exec-Time'] = str(end - start) return result return wrapper你当然可以手动把这个装饰器应用到每一条路由上:
from bottle import route @route("/timed") @stopwatch # <-- 能工作,但不要这样做 def timed(): time.sleep(1) return "DONE"但既然存在插件系统,就有更好的方式。
3.2 使用 install() 全局安装插件
在应用不断壮大的过程中,手动给每条路由加装饰器很快就会变得繁琐且易错。如果你真的想对所有路由生效,更简单的做法是使用install():
from bottle import route, install install(stopwatch) @route("/timed") def timed(): ...install()会注册一个插件,使其被自动应用到应用的所有路由上。调用install()是在绑定路由之前还是之后并不重要,所有插件始终会被应用到所有路由上;但多次install()的调用顺序很重要——如果安装了多个插件,它们会按照安装时的顺序依次应用。
任何能作为路由回调装饰器工作的可调用对象都是合法插件,包括普通装饰器、类、可调用实例,以及实现了扩展PluginAPI 的插件(详见 插件开发指南)。
3.3 使用 uninstall() 卸载插件
你可以按名称、类或实例卸载之前安装的插件:
sqlite_plugin = SQLitePlugin(dbfile='/tmp/test.db') install(sqlite_plugin) uninstall(sqlite_plugin) # 卸载指定的插件实例 uninstall(SQLitePlugin) # 卸载该类型的所有插件 uninstall('sqlite') # 卸载所有该名称的插件 uninstall(True) # 一次性卸载所有插件插件可以在任何时刻安装与移除,甚至可以在服务请求的运行时进行。插件是按需(on-demand)应用的——也就是说,路由第一次被请求时才真正应用插件。这带来了一些巧妙用法(例如只在需要时才安装慢速调试或性能分析插件),但不应过度使用:每次插件列表发生变化,路由缓存都会被清空,所有插件都需要重新应用。
注意:模块级的
install()/uninstall()函数作用于默认应用(default application)。要为某个特定应用管理插件,请使用对应Bottle实例上的同名方法。
源码佐证:install / uninstall 的实现
在 bottle.py 中,Bottle.install()会先调用插件上的setup(app)(若存在),并校验插件"可调用或实现了apply",然后追加到self.plugins列表并调用self.reset()使所有路由重新应用插件;Bottle.uninstall()则支持四种匹配方式——实例(remove is plugin)、类型(remove is type(plugin))、名称(getattr(plugin, 'name', True) == remove)以及True(全部移除),并在移除存在close()方法的插件后调用reset()。相关行为在 test/test_plugins.py 中有完整测试覆盖(按实例/类型/名称/全部四种卸载方式)。
3.4 按路由选择性应用或跳过插件
大多数插件足够"聪明",会自动忽略不需要其功能的路由且不增加任何开销;但当你需要精确控制时,也可以按路由应用或跳过指定插件。
只对单条路由应用某个装饰器或插件时,不要install()它,而是在route装饰器上使用apply关键字:
@route('/timed', apply=[stopwatch]) def timed(): ...路由级插件会最先被应用(先于应用级插件),除此之外与普通插件完全一致。
你还可以为部分路由显式禁用已安装的插件。route装饰器为此提供了skip参数:
install(stopwatch) @route('/notime', skip=[stopwatch]) def no_time(): passskip参数接受单个值或值列表,可以用插件名称、类或实例来标识要跳过的插件;设置skip=True则一次性跳过所有插件。
源码佐证:all_plugins 与 _make_callback
路由对插件的解析集中在 bottle.py。Route.all_plugins()按reversed(app.plugins + self.plugins)的顺序产出影响该路由的所有插件——这正是"路由级插件先于应用级插件应用、同级别按安装顺序逆序包裹"的来源;它同时处理skip匹配(True、实例、类型、名称)与同名插件去重。Route._make_callback()则对每个插件执行:若插件实现了apply就调用plugin.apply(callback, self),否则直接plugin(callback),并把包裹结果缓存为Route.call(bottle.py)。测试 test/test_plugins.py 验证了插件应用顺序以及按实例、按类、按名称、skip=True、非列表单值跳过的各种组合行为。
四、插件与挂载应用:Bottle.mount 的行为
大多数插件都只针对其被安装的那个应用,不应影响用Bottle.mount挂载进来的子应用。示例:
root = Bottle() root.install(plugins.WTForms()) root.mount('/blog', apps.blog) @root.route('/contact') def contact(): return template('contact', email='contact@example.com')挂载应用时,Bottle 会在挂载方应用上创建一个代理路由(proxy-route),把所有请求转发给被挂载的应用。插件默认在此类代理路由上被禁用。因此,上面虚构的WTForms插件会影响/contact路由,但不会影响/blog子应用的任何路由。
这是一个合理的默认行为,但可以被覆盖。下面的示例为某个特定代理路由重新激活所有插件:
root.mount('/blog', apps.blog, skip=None)注意:插件会把整个子应用视为一条路由,即上面提到的代理路由。如果想让子应用的每条独立路由都被包裹,你需要直接把插件安装到被挂载的应用上。
从源码看,挂载 WSGI 应用时默认传入options.setdefault('skip', True)(bottle.py),这正是"代理路由默认跳过插件"的实现依据;而mount(..., skip=None)可以显式取消这一限制。
五、配置插件:构造参数、应用配置与运行时切换
大多数插件接受通过构造函数参数传入的配置。这是最直接、最明显的配置方式,例如告诉数据库插件连接哪个数据库:
install(SQLitePlugin(dbfile='/tmp/test.db', ...))较新的插件还可能从Bottle.config中读取配置(见 docs/configuration.rst)。这对于那些希望在特定部署环境下易于修改或覆盖的配置非常有用,这种模式甚至支持通过配置钩子(config hooks)进行运行时变更:
app.config["sqlite.db"] = '/tmp/test.db' app.install(SQLitePlugin())插件还可以检查它们被应用到的路由,并为个别路由改变自身行为。插件作者对未装饰的路由回调、以及传给route装饰器的参数(包括那些 Bottle 本身忽略的自定义参数)拥有完整访问权,这提供了极大的灵活性。常见模式包括:
- 数据库插件:如果路由回调接受
db关键字参数,就自动开启一个事务; - 表单插件:忽略不监听
POST请求的路由; - 访问控制插件:检查传给
route装饰器的自定义roles_allowed参数。
以 Bottle 内置的JSONPlugin为例(bottle.py),它在setup(app)中通过app.config._define(...)定义了json.enable、json.ascii、json.indent、json.dump_func四个可配置项,并在apply()中读取配置、把返回dict的回调自动序列化为 JSON 响应。这也是"新插件通过应用配置运行时可调"的典型实现。
六、插件机制源码级解读:从装饰器到 Plugin API
6.1 任何可调用对象都是插件
任何接受一个函数并返回一个函数的可调用对象都是合法插件。但这种方式有局限性。需要更多上下文与控制的插件可以实现扩展的Plugin接口并挂接高级特性。需要强调的是,Plugin并不是一个可以从bottle模块导入的真实类,而是一份插件需要实现才能被识别为"扩展插件"的契约。
Plugin契约包含以下成员(全部为可选,除apply/__call__外):
| 成员 | 类型 | 说明 |
|---|---|---|
name | 属性 | 供Bottle.uninstall()和route(skip=...)按名称引用插件或插件类型。只有带name属性的插件才支持按名匹配 |
api | 属性 | 插件 API 仍在演进。这个整数值告诉 Bottle 使用哪个版本,缺省时默认使用第一版。当前版本为2 |
setup(self, app) | 方法 | 插件通过Bottle.install安装到应用时立即调用,唯一参数是应用对象。通过apply应用到路由的插件不会被调用此方法,只有安装到应用的插件才会 |
__call__(self, callback) | 方法 | 只要未定义apply,插件自身就被当作装饰器直接应用到每个路由回调上。若无需包裹或替换回调,直接返回未修改的 callback 参数即可 |
apply(self, callback, route) | 方法 | 若已定义,则优先于__call__用于装饰路由回调。额外的route参数是一个Route实例,提供了大量关于待装饰路由的上下文与元信息 |
close(self) | 方法 | 插件被卸载或应用被关闭时调用(见Bottle.uninstall/Bottle.close)。同样只对安装到应用的插件调用 |
从 bottle.py 的_make_callback可以确认这一优先级:源码先判断hasattr(plugin, 'apply'),有则走apply(callback, self),否则走plugin(callback)。测试 test/test_plugins.py 专门验证了"实现了apply的插件绝不会被当作普通可调用对象调用"。
6.2 Plugin API 版本演进
插件 API 仍在演进,并在 Bottle 0.10 时为解决路由上下文字典的某些问题发生了变化。为保证与 0.9 插件的向后兼容,引入了可选的Plugin.api属性:
- Bottle 0.9 · API 1(不设置
Plugin.api):即 0.9 文档描述的原始插件 API。 - Bottle 0.10 · API 2(
Plugin.api等于 2):Plugin.apply的context参数由上下文字典改为Route实例。
6.3 Route 上下文与运行时优化
传给Plugin.apply的Route实例提供了关于待装饰路由、原始路由回调及路由级配置的详细信息。
需要注意:Route.config是路由局部的,但在所有插件之间共享。为插件配置加一个唯一前缀、或在config字典中用独立的命名空间存放大量配置,是避免插件之间命名冲突的好习惯。
Route的部分属性是可变的,但改动可能对其它插件产生意外影响,并且只对尚未应用的插件生效。如果你需要做出所有插件都能识别的路由改动,应随后调用Route.reset():这会清空路由缓存,下次路由被调用时重新应用所有插件,给所有插件适应新配置的机会。不过路由器(router)不会被更新——修改rule或method值不会影响路由器,只会影响插件。
所有插件应用到一条路由后,包裹后的回调会被缓存以加速后续请求(Route.call是 bottle.py 中的cached_property)。如果插件行为依赖配置、且希望在运行时修改配置,你就需要在每次请求时读取配置——这很容易做到。出于性能考虑,也可以根据当前需要返回不同的包裹器、使用闭包,或干脆在运行时启用/禁用插件。以内置HooksPlugin为例:如果没有任何钩子被安装,插件会把自己从所有路由上移除,开销几乎为零;一旦安装了第一个钩子,插件便重新激活并生效。
要实现这种能力,你需要控制回调缓存:Route.reset()清空单条路由的缓存,Bottle.reset()一次性清空应用内所有路由的全部缓存(bottle.py)。下次请求到来时,所有插件会像路由第一次被请求时那样被重新应用。
6.4 插件编写的常见模式
- 依赖或资源注入:插件检查回调是否接受某个特定的关键字参数,只有存在该参数时才应用自己。例如,期待
db关键字参数的路由回调才需要数据库连接;不期待该参数的路由可以被跳过、不装饰。参数名应可配置,以免与其他插件或路由参数冲突。 - 请求上下文属性:插件可以向当前
request添加新的请求局部属性,例如用于持久会话的request.session、用于已登录用户的request.user。 - 响应类型映射:插件检查被包裹回调的返回值并转换成新类型。内置的
JsonPlugin正是如此(返回dict时序列化为 JSON 并设置application/json内容类型)。 - 零开销插件:在特定路由上不需要的插件应原样返回回调;若想在运行时从某路由上移除自己,可以调用
Route.reset(),下次该路由触发时将其跳过。 - 请求前后处理:插件可以成为
before_request/after_request钩子(见Bottle.add_hook)的便捷替代品,尤其是两者都需要时。
七、实战:从零编写一个 SQLite 插件
下面这个完整示例来自 插件开发指南:它向被包裹的回调额外提供一个 sqlite3 数据库连接句柄,但仅当回调确实期待该参数时;否则该路由被忽略、不增加任何开销。包裹器不改变返回值,但妥善处理插件相关异常;setup()用于检查应用并寻找可能冲突的插件。
import sqlite3 import inspect class SQLitePlugin: name = 'sqlite' api = 2 def __init__(self, dbfile=':memory:', autocommit=True, dictrows=True, keyword='db'): self.dbfile = dbfile self.autocommit = autocommit self.dictrows = dictrows self.keyword = keyword def setup(self, app): ''' Make sure that other installed plugins don't affect the same keyword argument.''' for other in app.plugins: if not isinstance(other, SQLitePlugin): continue if other.keyword == self.keyword: raise PluginError("Found another sqlite plugin with "\ "conflicting settings (non-unique keyword).") def apply(self, callback, route): # Override global configuration with route-specific values. conf = route.config.get('sqlite') or {} dbfile = conf.get('dbfile', self.dbfile) autocommit = conf.get('autocommit', self.autocommit) dictrows = conf.get('dictrows', self.dictrows) keyword = conf.get('keyword', self.keyword) # Test if the original callback accepts a 'db' keyword. # Ignore it if it does not need a database handle. args = inspect.getargspec(route.callback)[0] if keyword not in args: return callback def wrapper(*args, **kwargs): # Connect to the database db = sqlite3.connect(dbfile) # This enables column access by name: row['column_name'] if dictrows: db.row_factory = sqlite3.Row # Add the connection handle as a keyword argument. kwargs[keyword] = db try: rv = callback(*args, **kwargs) if autocommit: db.commit() except sqlite3.IntegrityError, e: db.rollback() raise HTTPError(500, "Database Error", e) finally: db.close() return rv # Replace the route callback with the wrapped one. return wrapper这个插件虽以示例为目的,但确实可以直接使用:
sqlite = SQLitePlugin(dbfile='/tmp/test.db') bottle.install(sqlite) @route('/show/<page>') def show(page, db): row = db.execute('SELECT * from pages where name=?', page).fetchone() if row: return template('showpage', page=row) return HTTPError(404, "Page not found") @route('/static/<fname:path>') def static(fname): return static_file(fname, root='/some/path') @route('/admin/set/<db:re:[a-zA-Z]+>', skip=[sqlite]) def change_dbfile(db): sqlite.dbfile = '/tmp/%s.db' % db return "Switched DB to %s.db" % db三条路由演示了三种典型交互:
- 第一条路由需要数据库连接,通过声明
db关键字参数让插件为它创建句柄; - 第二条路由不需要数据库,因此被插件忽略;
- 第三条路由确实期待
db关键字参数,但显式跳过 sqlite 插件——这样该参数不会被插件接管,仍然保留同名 URL 参数的值。
提示:该示例写作年代较早,
inspect.getargspec在较新的 Python 版本中已废弃;现代实现可用inspect.signature(route.callback)(源码中的Route.get_callback_args()即采用此方式,见 bottle.py)替代。
八、结语与延伸阅读
第三方插件是 Bottle 生态的重要组成部分:官方维护的 插件清单 覆盖了会话缓存、认证授权、数据库 ORM、OAuth2、模板渲染、静态文件、参数校验、错误处理等主流场景;而统一而灵活的插件机制(install/uninstall/apply/skip与PluginAPI)让这些插件的接入与扩展都极其轻量。
如需继续深入,建议阅读:
- 使用插件指南:插件的基础概念、安装/卸载与按路由应用/跳过的完整说明;
- 插件开发指南:
PluginAPI 契约、API 版本演进、Route 上下文与常见编写模式; - 应用配置:
Bottle.config与配置钩子,供插件读取运行时配置; - bottle.py:
install/uninstall/reset的源码实现; - bottle.py:内置
JSONPlugin与TemplatePlugin,是扩展PluginAPI 的第一方范例; - test/test_plugins.py:插件安装、卸载、顺序、跳过、API 各方法的完整测试用例。
最后再次强调官方清单中的免责声明:上述第三方插件均非 Bottle 项目的一部分,而是由第三方开发与维护;在生产环境中选用前,请务必查阅各自项目的文档与发布说明,确认其维护状态与版本兼容性。
- 后端
- Web框架
【免费下载链接】bottle
bottle.py is a fast and simple micro-framework for python web-applications.
相关推荐
RocksDB 插件机制与第三方插件生态全指南:从官方插件清单到构建系统源码解析
RocksDB 插件机制与第三方插件生态全指南:从官方插件清单到构建系统源码解析 RocksDB 是一款可嵌入、持久化的键值存储引擎,其强大的可定制性不仅体现在
数据库KV存储嵌入式数据库存储Capistrano 第三方插件生态指南:从官方插件清单到 install_plugin 源码原理
Capistrano 第三方插件生态指南:从官方插件清单到 install_plugin 源码原理 Capistrano 是基于 Ruby、Rake 与 SSH
DevOpsCLIPillow 第三方插件生态指南:插件模型、注册机制与实战扩展
Pillow 第三方插件生态指南:插件模型、注册机制与实战扩展 Pillow(Python Imaging Library)通过一套成熟的 插件模型(plugi
图像处理计算机视觉
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考