Package: plone.jsonapi.core
plone.jsonapi.coreAuthor:Ramon BartlVersion:0.7.0Table of Contentsplone.jsonapi.coreLatest Build StatusAbstractIntroductionMotivationLimitationsCompatibilityInstallationAPI URLAPI FrameworkThe API RouterAPI URLsPermissionsPlone JSONAPI Integration TestsChangelog0.7.0 - 2020-03-290.6 - 2017-01-100.5 - 2015-07-090.4 - 2014-03-040.3 - 2014-01-230.2 - 2013-08-110.1 - unreleasedLatest Build StatusAbstractAn extensible Plone JSON API FrameworkIntroductionThis Package allows Users to expose content information via JSON.MotivationThis project was born in 2012, out of the need for a data source to build a
network based iOS application. Or more precise, I wanted to learn iOS
programming and wanted to knit my own JSON API:)I know, it is a little bit awkward to provide an own routing mechanism for
Plone which dipatches the request after theZPublisherdid its job, but it
worked and thus, I did it.LimitationsSince the API comes after theZPublisher, it can only make use ofHTTP GETandHTTP POSTmethods. The other methods will never reach the API
View.Be aware that the API View comes with the permissionzope2.View, so you need
to programmatically check for the correct permissions on your custom routes.See:http://developer.plone.org/security/permission_lists.htmlCompatibilityTheplone.jsonapi.coreshould work withPlone3, 4 and 5 on Python 3.NoteUsers ofPlone3 should pin the versions ofsimplejsonandWerkzeug.Example:[buildout]
...
versions = versions

[versions]
...
simplejson = 2.0.9
werkzeug = 0.7.2InstallationThere official release is on pypi, so you have to simply includeplone.jsonapi.coreto your buildout config.Example:[buildout]
...

[instance]
...
eggs =
    ...
    plone.jsonapi.coreAPI URLAfter installation, the API View is available as a Browser View on your Plone
site with the name@@API, for examplehttp://localhost:8080/Plone/@@API.API FrameworkThe main work is done in theplone.jsonapi.core.browser.apimodule.  This
module dispatches the incoming request and dispatches it to an endpoint
function.The API RouterTheRouteris responsible to manage and maintain API routes to endpoints.Routes get defined by so called “Route Providers”.A route provider is either a named Utility class, which implements theIRouteProviderinterface, or simply a function, which is registered
via theadd_routedecorator.Basic ExampleThe most basic route provider is simply a decorated function:from plone.jsonapi.core import router

@router.add_route("/hello/<string:name>", "hello", methods=["GET"])
def hello(context, request, name="world"):
    return {"hello": name}The passed in context and request gets passed of the@@APIView.
It can be used to query Plone tools or other utilities or adapters.A more complex ExampleIn this Example, we’re going to add a route provider namedmy_routes.
This route provider gets registered as an namedUtility.To do so, we add a module calledroutes.pyto our package and add the
following code: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}To register theUtility, we add this directive to theconfigure.zcmlfile:<!-- Extension point for custom routes -->
<utility
    name="my_routes"
    provides="plone.jsonapi.core.interfaces.IRouteProvider"
    factory=".routes.ExampleRoutes" />Or use grok:from five import grok

...

grok.global_utility(ExampleRoutes, name="my_routes", direct=False)Each route provider gets initialized with thecontextand therequestin a
method calledinitialize. This method gets called by the API framework.Our route provider has to contain aroutesproperty or method. It should
return a tuple of route definitions. Each route definition contains the url
rule (/hello), an endpoint name (hello), a method to be called when the url
matches (self.json_hello) and an additional dictionary with routingoptionsTheoptionsdictionary get directly passed to the routing mechanism ofWerkzeug.
For details, see:http://werkzeug.pocoo.org/docs/routing/#rule-formatNoteplone.jsonapi.corecomes with a default implementation of the router.
This router uses the routing mechanism provided byWerkzeug.
It is possible to plug in a more sophisticated router by using the ZCA.
Simply configure a class which implements theIRouterinterface.To test this route, browse to the/helloAPI url:http://localhost:8080/Plone/@@API/hello/JSON%20Plone%20APIResult:{
    _runtime: 0.00025200843811035156,
    hello: "JSON Plone API"
}API URLsIf you design your custom RESTful JSON API, you probably want to insert URLs to
your specified resources, e.g:http://localhost:8080/Plone/@@API/news/news_items_1Theplone.jsonapi.core.routermodule comes with aurl_formethod.So when you want to insert the URL for the definedhelloendpoint, you simply
add it like this: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,
    }It builds the URLs using thebuildmethod of the MapAdapter ofWerkzeug.
For details, seehttp://werkzeug.pocoo.org/docs/routing/#werkzeug.routing.MapAdapter.buildThe resulting JSON will look like this:http://localhost:8080/Plone/@@API/hello/worldResult:{
    url: "http://localhost:8080/Plone/@@API/hello/world",
    runtime: 0.002997875213623047,
    hello: "world"
}PermissionsYou have to handle the permissions for your routes manually.
so if you would like to restrict the permission of thehelloroute,
you have to do something like this: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,
    }Output:{
    runtime: 0.0021250247955322266,
    success: false,
    error: "You don't have the 'ViewHelloAPI' permission"
}Plone JSONAPI Integration TestsWithplone.jsonapi.coreenabled, it is simple to expose functions within
Plone. You only have to wrap your function around the@router.add_routedecorator.The following doctest will demonstrate how the framework works and how to
register new routes.Some needed imports:>>> import json
>>> from plone.jsonapi.core import router
>>> from plone.jsonapi.core.version import versionPrepare the browser:>>> browser = self.getBrowser()Remember some URLs:>>> portal = self.getPortal()
>>> portal_url = portal.absolute_url()
>>> api_url = portal_url + "/@@API"
>>> version_url = api_url + "/version"Check if the version URL returns the right version:>>> browser.open(version_url)
>>> dct = json.loads(browser.contents)
>>> dct["url"] == version_url
True
>>> dct["version"] == version()
TrueTesting the framework – lets add a new GET route:>>> @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'Testing the framework – lets add a new POST route:>>> @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'Check what happenss when a route throws an Error:>>> @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'Test 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>'Test Binary Stream:>>> @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-...'Changelog0.7.0 - 2020-03-29https://github.com/collective/plone.jsonapi.core/pull/23Add Python3 compatibility0.6 - 2017-01-10Supports XML response.
Use the request parameterasxml=1or set the requestAcceptheader toapplication/xmlhttps://github.com/collective/plone.jsonapi.core/issues/21Support file streams.
Use the request parameterasbinary=1or set the requestAcceptheader toapplication/ziphttps://github.com/collective/plone.jsonapi.core/issues/22Do not store the request on the router upon initializationhttps://github.com/collective/plone.jsonapi.core/issues/18Handle None valueshttps://github.com/collective/plone.jsonapi.core/issues/17Print out Traceback’s to the console and not back to the client0.5 - 2015-07-09https://github.com/collective/plone.jsonapi.core/pull/14useurlsplit(request.get("ACTUAL_URL","")).netlocto get the hostnameadded more testschanged info to debug logging to reduce verbositysmoe minor code cleanup0.4 - 2014-03-04https://github.com/ramonski/plone.jsonapi.core/issues/10add the traceback to the response when an error occurshttps://github.com/ramonski/plone.jsonapi.core/issues/7started with doctests0.3 - 2014-01-23renamed package toplone.jsonapi.coredue to namespace conflicts withplone.jsonapi.routesremoved default plone route configuration.addedversionroutechanged theurl_formethod of the router to provide correct urls for
virtual hosting.0.2 - 2013-08-11Router implementation updated to support decorated functions as route
providers.url_for functionality implementeddocumentation changed0.1 - unreleasedinitial start of development