ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Bottle 第三方插件生态指南:插件清单、安装管理与源码级机制解析

Bottle 第三方插件生态指南:插件清单、安装管理与源码级机制解析 后端Web框架【免费下载链接】bottlebottle.py is a fast and simple micro-framework for python web-applications.项目地址https://gitcode.com/gh_mirrors/bo/bottle点击查看免费下载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-jwtbottle.py 的 JSON Web TokenJWT认证插件。bottlejwtBottle 的 JWT 集成。canister如上所述同时覆盖认证场景。跨域CORSBottle-Cors-plugin在 Bottle Web 应用中实现 CORS 的最简单方式。辅助元包Bottle-Extras元包Meta package用于一次性安装整个 Bottle 插件集合。闪现消息FlashBottle-FlashBottle 的 flash 消息插件用于在请求间传递一次性提示信息。队列Bottle-Hotqueue构建于 Redis 之上的 Bottle FIFO 队列。数据库与 ORMMacaron一个面向 SQLite 的对象关系映射器ORM。Bottle-MemcacheBottle 的 Memcache 集成。Bottle-MongoBottle 的 MongoDB 集成。Bottle-RedisBottle 的 Redis 集成。Bottle-SqlalchemyBottle 的 SQLAlchemy 集成。Bottle-SqliteBottle 的 SQLite3 数据库集成。Bottle-Web2pydalBottle 的 Web2py Dal 集成。OAuth2.0Bottle-OAuthliboauthlib 的适配器用于创建你自己的 OAuth2.0 实现。模板渲染Bottle-RendererBottle 的渲染器插件。静态文件服务Bottle-Servefiles一个可复用的应用为 Bottle 应用提供静态文件服务。WSGI / 请求与响应对象增强Bottle-Werkzeug集成werkzeug库提供替代的 request/response 对象、高级调试中间件等。查询参数解析与数据校验bottle-smart-filtersBottle 查询字符串querystring的智能猜测。bottle-cerberusBottle 的 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-FlashJSON 错误响应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参数接受单个值或值列表可以用插件名称、类或实例来标识要跳过的插件设置skipTrue则一次性跳过所有插件。源码佐证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.callbottle.py。测试 test/test_plugins.py 验证了插件应用顺序以及按实例、按类、按名称、skipTrue、非列表单值跳过的各种组合行为。四、插件与挂载应用Bottle.mount 的行为大多数插件都只针对其被安装的那个应用不应影响用Bottle.mount挂载进来的子应用。示例root Bottle() root.install(plugins.WTForms()) root.mount(/blog, apps.blog) root.route(/contact) def contact(): return template(contact, emailcontactexample.com)挂载应用时Bottle 会在挂载方应用上创建一个代理路由proxy-route把所有请求转发给被挂载的应用。插件默认在此类代理路由上被禁用。因此上面虚构的WTForms插件会影响/contact路由但不会影响/blog子应用的任何路由。这是一个合理的默认行为但可以被覆盖。下面的示例为某个特定代理路由重新激活所有插件root.mount(/blog, apps.blog, skipNone)注意插件会把整个子应用视为一条路由即上面提到的代理路由。如果想让子应用的每条独立路由都被包裹你需要直接把插件安装到被挂载的应用上。从源码看挂载 WSGI 应用时默认传入options.setdefault(skip, True)bottle.py这正是代理路由默认跳过插件的实现依据而mount(..., skipNone)可以显式取消这一限制。五、配置插件构造参数、应用配置与运行时切换大多数插件接受通过构造函数参数传入的配置。这是最直接、最明显的配置方式例如告诉数据库插件连接哪个数据库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 API6.1 任何可调用对象都是插件任何接受一个函数并返回一个函数的可调用对象都是合法插件。但这种方式有局限性。需要更多上下文与控制的插件可以实现扩展的Plugin接口并挂接高级特性。需要强调的是Plugin并不是一个可以从bottle模块导入的真实类而是一份插件需要实现才能被识别为扩展插件的契约。Plugin契约包含以下成员全部为可选除apply/__call__外成员类型说明name属性供Bottle.uninstall()和route(skip...)按名称引用插件或插件类型。只有带name属性的插件才支持按名匹配api属性插件 API 仍在演进。这个整数值告诉 Bottle 使用哪个版本缺省时默认使用第一版。当前版本为2setup(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 2Plugin.api等于 2Plugin.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:, autocommitTrue, dictrowsTrue, keyworddb): self.dbfile dbfile self.autocommit autocommit self.dictrows dictrows self.keyword keyword def setup(self, app): Make sure that other installed plugins dont 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, pagerow) 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.pyinstall/uninstall/reset的源码实现bottle.py内置JSONPlugin与TemplatePlugin是扩展PluginAPI 的第一方范例test/test_plugins.py插件安装、卸载、顺序、跳过、API 各方法的完整测试用例。最后再次强调官方清单中的免责声明上述第三方插件均非 Bottle 项目的一部分而是由第三方开发与维护在生产环境中选用前请务必查阅各自项目的文档与发布说明确认其维护状态与版本兼容性。赞分享后端Web框架【免费下载链接】bottlebottle.py is a fast and simple micro-framework for python web-applications.项目地址https://gitcode.com/gh_mirrors/bo/bottle点击查看免费下载相关推荐RocksDB 插件机制与第三方插件生态全指南从官方插件清单到构建系统源码解析RocksDB 插件机制与第三方插件生态全指南从官方插件清单到构建系统源码解析 RocksDB 是一款可嵌入、持久化的键值存储引擎其强大的可定制性不仅体现在数据库KV存储嵌入式数据库存储Capistrano 第三方插件生态指南从官方插件清单到 install_plugin 源码原理Capistrano 第三方插件生态指南从官方插件清单到 install_plugin 源码原理 Capistrano 是基于 Ruby、Rake 与 SSHDevOpsCLIPillow 第三方插件生态指南插件模型、注册机制与实战扩展Pillow 第三方插件生态指南插件模型、注册机制与实战扩展 PillowPython Imaging Library通过一套成熟的 插件模型plugi图像处理计算机视觉上一篇Xray构建错误速查指南10个常见编译问题与快速解决方案下一篇三分钟解锁Mac鼠标隐藏技能Sensible Side Buttons让你的第三方鼠标侧键复活创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表