Plone JSON API
项目描述
plone.jsonapi.core
- 作者:
Ramon Bartl
- 版本:
0.7.0
最新构建状态
摘要
可扩展的Plone JSON API框架
介绍
此包允许用户通过JSON公开内容信息。
动机
此项目始于2012年,源于构建基于网络的iOS应用程序的数据源需求。或者更准确地说,我想学习iOS编程,并想自己构建JSON API:)
我知道,为Plone提供自己的路由机制有点尴尬,因为该机制在ZPublisher执行其工作后分发请求,但它是有效的,所以我这样做了。
限制
由于API在ZPublisher之后,因此它只能使用HTTP GET和HTTP POST方法。其他方法永远不会到达API视图。
请注意,API视图具有权限zope2.View,因此您需要在自定义路由上以编程方式检查正确的权限。
参见:http://developer.plone.org/security/permission_lists.html
兼容性
plone.jsonapi.core 应该与 Python 3 上的 Plone 3、4 和 5 兼容。
示例
[buildout] ... versions = versions [versions] ... simplejson = 2.0.9 werkzeug = 0.7.2
安装
官方发布版位于 pypi,因此您只需将 plone.jsonapi.core 包含到您的 buildout 配置中即可。
示例
[buildout] ... [instance] ... eggs = ... plone.jsonapi.core
API URL
安装后,API 视图将以名为 @@API 的浏览器视图的形式在您的 Plone 网站上可用,例如 http://localhost:8080/Plone/@@API。
API框架
主要工作在 plone.jsonapi.core.browser.api 模块中完成。该模块负责转发传入的请求并将其发送到端点函数。
API路由器
路由器 负责管理和维护 API 路由到端点。
路由由所谓的“路由提供者”定义。
路由提供者可以是实现 IRouteProvider 接口的命名实用程序类,也可以是一个通过 add_route 装饰器注册的简单函数。
基本示例
最简单的路由提供者只是一个装饰过的函数
from plone.jsonapi.core import router @router.add_route("/hello/<string:name>", "hello", methods=["GET"]) def hello(context, request, name="world"): return {"hello": name}
传入的上下文和请求会被转发到 @@API 视图。它可以用来查询 Plone 工具或其他实用程序或适配器。
更复杂的示例
在这个示例中,我们将添加一个名为 my_routes 的路由提供者。这个路由提供者被注册为一个命名的 实用程序。
为此,我们在我们的包中添加一个名为 routes.py 的模块,并添加以下代码
from zope import interface from plone.jsonapi.core.interfaces import IRouteProvider class ExampleRoutes(object): interface.implements(IRouteProvider) def initialize(self, context, request): """ called by the json api framework""" pass @property def routes(self): return ( ("/hello/<string:name>", "hello", self.json_hello, dict(methods=['GET'])), ) def json_hello(self, context, request, name="world"): return {"hello": name}
要注册这个 实用程序,我们在 configure.zcml 文件中添加以下指令
<!-- Extension point for custom routes --> <utility name="my_routes" provides="plone.jsonapi.core.interfaces.IRouteProvider" factory=".routes.ExampleRoutes" />
或者使用 grok
from five import grok ... grok.global_utility(ExampleRoutes, name="my_routes", direct=False)
每个路由提供者都会在一个名为 initialize 的方法中初始化上下文和请求。该方法由 API 框架调用。
我们的路由提供者必须包含一个 routes 属性或方法。它应该返回一个路由定义的元组。每个路由定义包含 URL 规则(/hello)、端点名称(hello)、当 URL 匹配时要调用的方法(self.json_hello)以及一个包含路由 options 的附加字典
options 字典会直接传递给 Werkzeug 的路由机制。有关详细信息,请参阅: http://werkzeug.pocoo.org/docs/routing/#rule-format
要测试此路由,请浏览到 /hello API URL
http://localhost:8080/Plone/@@API/hello/JSON%20Plone%20API
结果
{ _runtime: 0.00025200843811035156, hello: "JSON Plone API" }
API URL
如果您设计自定义的 RESTful JSON API,您可能希望插入指定资源的 URL,例如
http://localhost:8080/Plone/@@API/news/news_items_1
plone.jsonapi.core.router 模块包含一个 url_for 方法。
因此,当您要插入定义的 hello 端点的 URL 时,您可以像这样添加它
from plone.jsonapi.core import router @router.add_route("/hello/<string:name>", "hello", methods=["GET"]) def hello(context, request, name="world"): return { "url": router.url_for("hello", values={"name": name}, force_external=True), "hello": name, }
它使用Werkzeug的Werkzeug库中的MapAdapter的build
方法来构建URL。详细信息,请参阅http://werkzeug.pocoo.org/docs/routing/#werkzeug.routing.MapAdapter.build
生成的JSON将如下所示
http://localhost:8080/Plone/@@API/hello/world
结果
{ url: "http://localhost:8080/Plone/@@API/hello/world", runtime: 0.002997875213623047, hello: "world" }
权限
您必须手动处理路由的权限。因此,如果您想限制hello
路由的权限,您必须执行如下操作
from AccessControl import getSecurityManager from AccessControl import Unauthorized from plone.jsonapi.core import router @router.add_route("/hello/<string:name>", "hello", methods=["GET"]) def hello(context, request, name="world"): if not getSecurityManager().checkPermission("ViewHelloAPI", context): raise Unauthorized("You don't have the 'ViewHelloAPI' permission") return { "url": router.url_for("hello", values={"name": name}, force_external=True), "hello": name, }
输出
{ runtime: 0.0021250247955322266, success: false, error: "You don't have the 'ViewHelloAPI' permission" }
Plone JSONAPI集成测试
启用plone.jsonapi.core后,在Plone中公开函数变得很简单。您只需将函数包装在@router.add_route装饰器中。
以下doctest将展示框架的工作原理以及如何注册新路由。
一些必要的导入
>>> import json >>> from plone.jsonapi.core import router >>> from plone.jsonapi.core.version import version
准备浏览器
>>> browser = self.getBrowser()
记住一些URL
>>> portal = self.getPortal() >>> portal_url = portal.absolute_url() >>> api_url = portal_url + "/@@API" >>> version_url = api_url + "/version"
检查版本URL是否返回正确的版本
>>> browser.open(version_url) >>> dct = json.loads(browser.contents) >>> dct["url"] == version_url True >>> dct["version"] == version() True
测试框架 - 添加新的GET路由
>>> @router.add_route("/hello/<string:name>", "hello", methods=["GET"]) ... def hello(context, request, name="world"): ... return dict(hello=name) >>> browser.open(api_url + "/hello/world") >>> json.loads(browser.contents).get("hello") 'world'
测试框架 - 添加新的POST路由
>>> @router.add_route("/hello", "hello_post", methods=["POST"]) ... def hello_post(context, request): ... return {"hello": "post"} >>> browser.post(api_url + "/hello", "") >>> json.loads(browser.contents).get("hello") 'post'
检查当路由抛出错误时会发生什么
>>> @router.add_route("/fail", "fail", methods=["GET"]) ... def fail(context, request): ... raise RuntimeError("This failed badly") >>> browser.open(api_url + "/fail") Traceback ... >>> json.loads(browser.contents).get("message") 'This failed badly'
测试XML
>>> @router.add_route("/xml", "xml", methods=["GET"]) ... def xml(context, request): ... return {"type": "xml"} >>> browser.open(api_url + "/xml?asxml=1") >>> browser.contents b'<?xml version="1.0" encoding="UTF-8" ?><root><type type="str">xml</type></root>'
测试二进制流
>>> @router.add_route("/data", "data", methods=["GET"]) ... def data(context, request): ... return self.get_testfile_path() >>> browser.open(api_url + "/data?asbinary=1") >>> browser.contents b'%PDF-...'
变更日志
0.7.0 - 2020-03-29
0.6 - 2017-01-10
支持XML响应。使用请求参数asxml=1或将请求Accept头设置为application/xml
https://github.com/collective/plone.jsonapi.core/issues/21 支持文件流。使用请求参数asbinary=1或将请求Accept头设置为application/zip
https://github.com/collective/plone.jsonapi.core/issues/22 在初始化时不将请求存储在路由器上
https://github.com/collective/plone.jsonapi.core/issues/18 处理None值
https://github.com/collective/plone.jsonapi.core/issues/17 将Traceback打印到控制台而不是发送回客户端
0.5 - 2015-07-09
https://github.com/collective/plone.jsonapi.core/pull/14 使用
urlsplit(request.get("ACTUAL_URL", "")).netloc
获取主机名添加了更多测试
将日志级别从info改为debug以减少冗余
进行了轻微的代码清理
0.4 - 2014-03-04
https://github.com/ramonski/plone.jsonapi.core/issues/10 当发生错误时,将堆栈跟踪添加到响应中
https://github.com/ramonski/plone.jsonapi.core/issues/7 开始使用doctests
0.3 - 2014-01-23
由于与plone.jsonapi.routes的命名空间冲突,将包重命名为plone.jsonapi.core
删除了默认的plone路由配置。
添加了version路由
将路由器的
url_for
方法更改为提供虚拟主机正确的URL。
0.2 - 2013-08-11
路由器实现更新以支持装饰函数作为路由提供者。
实现了url_for功能
更新了文档
0.1 - 未发布
初始开发开始