-
Notifications
You must be signed in to change notification settings - Fork 0
How to create a Phovea REST API
Table of Contents
In this tutorial you will learn how to register a new REST API namespace in the Phovea/TDP server.
This tutorial requires you to complete the following steps / tutorials:
cd <workspace_directory>
cd myserverplugin
yo phovea:add-extension
# type: namespace, id: hello-rest, filename: hello_rest
# extras: namespace=/api/hello_rest
# change myserverplugin/hello_rest.py according to the snippet below
# start/restart server
# access http://localhost:8080/api/hello_rest
# or
# access http://localhost:9000/api/hello_rest
Phovea allows to register additional REST namespaces via the namespace
extension point. During startup the server looks up all extensions of this extension point and reserves a namespace for it. A namespace in this context is a unique server URL prefix, such as api/hello_world
. By convention all server namespaces have to start with /api
followed by unique name. The TDP core itself registers a new REST namespace under /api/tdp
. Under this namespace all TDP core REST services are located.
REST services are implemented using the Flask framework. Flask allows to easily map HTTP URLs to python functions. In addition, for convenience Phovea is wrapping and reexporting methods from Flask avoiding a direct dependency on it. Therefore, the important module import reduces to phovea_server.ns
.
As for every extension, we first need to register a new extension using the Phovea Yeoman generator.
cd <workspace_directory>
cd myserverplugin
yo phovea:add-extension
The extension type is namespace
. ID something like hello_rest
and the filename should be hello_rest
. Following extra parameter are needed:
namespace=/api/hello_rest
images/phovea_rest_api/image1.png
In this example, we reserve the namespace /api/hello_rest
such that all HTTP requests starting with /api/hello_rest
are redirect to the newly created myserverplugin/hello_rest.py
plugin. In addition, the URL is truncated such that the extension doesn’t have to consider the /api/hello_rest
prefix. Thus /api/hello_rest/test
will be mapped to /test
.
The content of myserverplugin/hello_rest.py
should be replaced with the following snippet
from phovea_server.ns import Namespace
from phovea_server.util import jsonify
import logging
app = Namespace(__name__)
_log = logging.getLogger(__name__)
@app.route('/')
def _hello():
return jsonify({
'message': 'Hello Rest'
})
def create():
return app
At the beginning the phovea_server.ns
namespace module is imported which is Phovea wrapper around Flask. In addition the jsonify
method of phovea_server.util
is used to convert a Python dictionary/array to a JSON compatible HTTP response. Phovea’s internal jsonify
method is an improved method of Flasks internal jsonify
method providing support for serializing numpy and pandas objects, too. In addition the fast ujson
module is used to serialize the data resulting in a performance improvement.
Then, a namespace application object is created being the central management tool for the REST api. The app
instance is returned as part of the create
method. The create
method is used by Phovea as the central point to instantiate a new Python extension. The Namespace class is a wrapper around Flask allowing to use the same methods and annotations to register new URL matching pattern. In this example, a simple pattern matching the root of the REST API is implemented. It returns a simple JSON object containing a simple hello rest message.
The newly created REST API can be tested via two different URLs. First via http://localhost:8080/api/hello_rest. Second via http://localhost:9000/api/hello_rest
Both return the same result: images/phovea_rest_api/image3.png images/phovea_rest_api/image4.png
The difference is the following. Internally the Phovea server running within a docker container is accessible via port 9000. In addition, the internal development test server started using npm start:...
has a configured proxy such that all requests starting with /api/
are internally proxied to the Phovea server running at port 9000. Thus, http://localhost:8080/api/hello_rest will internally be proxied to http://localhost:9000/api/hello_rest. The proxy within the development server as well as the deployed NGINX server is used to avoid the need for cross-origin resource sharing (CORS). Usually when a website is requesting services from another domain such as port 8080 is using services from port 9000 the target server has to explicitly allow this source domain, otherwise the web browser blocks for security reasons the operation. However, by using a proxy both REST API and the website itself are coming from the same domain avoiding this problem.
Usually REST API use variable paths and request arguments to generate the response. The following snippet show a simple example how to access request parameters as well as do URL matching. More information can be found in the Flask documentation.
from phovea_server.ns import Namespace, request, abort
# ...
@app.route('/greet/<name>')
def _greet(name):
lang = request.values.get('lang', 'en')
template = ''
if lang == 'en':
template = 'Hello {name}'
elif lang == 'de':
template = 'Hallo {name}'
elif lang == 'es':
template = 'Hola {name}'
else:
abort(400, 'invalid language: ' + lang)
return jsonify({
'message': template.format(name=name)
})
# ...
def create():
return app
First, we have to import additional elements from the phovea_server.ns
module: request
for accessing request parameters and abort
for aborting requests and returning HTTP error codes.
The <name>
syntax is used to perform URL matching. In this case the next URL part after /greet/
should be provided as a function argument using the name name
. For example, /api/hello_rest/greet/datavisyn
will result in executing the _greet
method with name=datavisyn
as argument.
request.values
provides access to both GET and POST arguments. Internally it is a Multidict having the same interface as a regular dictionary with additions for handling multiple values for one key. The .get(name, default_value)
allows to provide default values for a dictionary as opposed to [name]
which will result in an exception in case the key doesn’t exist. The greeting method accepts the lang
parameter which specifies in which language the name should be greeted. In case of an unknown language abort(http_error_code, error_message’)
is used to abort the request and return the HTTP 400 status code = Bad Request.
images/phovea_rest_api/image7.png images/phovea_rest_api/image6.png images/phovea_rest_api/image2.png
Expert knowledge
Flask URL matching logic has several options to specify the type of the path element, for example, <test:int>
will automatically parse the matching URL part as integer. <test:path>
is a variant of a string that also allows /
as part of the pattern, such that /greet/<name:path>
with /greet/tom/jones
result in name=tom/jones
without the :path
annotation, Flask will throw an error
Lessons learned:
- How to register a new REST API using the namespace extension
- How to test the REST API via two different URLs
- How to access Request parameters and perform URL matching
-
How to create a mapping provider
In this tutorial you will learn how to create a backend mapping provider that maps one IDType into another. There are several way how to register such an provider, one of them is registering a new backend extension. -
How to create a D3 view
In this tutorial you will learn how to create a D3 visualization view. While in this tutorial a standard TDP REST api is used, it can be easily changed to use a custom REST api instead. For example, to perform data preparation on the server side instead of the client side.