14. Integration

New in version 16.02.

Changed in version 16.09.

https://img.shields.io/badge/release-16.09_beta-yellow.svg

14.1. Integration Overview

In order to integrate both internal products (such as the Trackit 8 Web front-end and Trackit 8 Mobile Apps), and external products (such as a customer’s own specific product), Trackit provides two main strands of API.

  1. Data API - RESTful JSON API for retrieval/modification of data structures, based on Django Rest Framework. This is typically used for manipulation of data within the main application, and the mobile application, via standard XMLHttpRequests. Connectors are implemented for our backbone.js models within the basis module static files.

  2. Application API - WSDL/JSONRPC API for programmatic and function calls, based on Spyne.

    2.1 This is used to implement Remote Procedure Calls. 2.2 This could be anything from logging in, to retrieving or executing specific commands. 2.3 It defines a set of commands which can be called using WSDL and or JSONRPC.

14.1.1. Application API Autodiscovery

Spyne is a protocol agnostic library which allows quick and easy implementation of RPC based APIs in a Pythonic manner. It has good integration with Django, and is an excellent fit for the application. It also generates WSDL schemas for use with external SOAP clients.

The library provides a variety of transports and protocols which can be utilised without needing to implement extra code. Protocols include SOAP 1.1 and JSONDocument, and can be “mixed and matched” in terms of input and output documents. In order to simplify development, the trackit/trackit_api folder within the main trackit module provides API autodiscovery within the AppConfig ready method. This builds on the Spyne Django implementation, and automatically discovers any Spyne methods within a module called api.py in any INSTALLED_APPS. The process is as follows:

  1. When Django calls the ready method, check that the API_ENABLED Django setting is True. If so, proceed to autodiscovery.

  2. Iterate all of the installed AppConfigs. For each iteration, check if an api.py module exists within the application root folder.

  3. If so, then iterate through each member of the module, and check if it’s a Spyne service by checking if the member is an instance of ServiceBase or DjangoServiceBase.

    • Where possible, spyne.util.django.DjangoServiceBase should be used as a service base class, as this implements some additional exception handling specifically for Django errors, such as validation, and re-throws them as the relevant Spyne error. Please see the API Service Example that follows.
  4. Look at the API_ENABLED_PROTOCOLS Django setting to determine what RPC protocols Trackit should create. This will very easily enable us to suppor additional protocols in the future, such as Yaml or MessagePack.

    • A protocol consists of three components:

      • An input protocol. This determines the format/protocol that data supplied by an API call should use. For example, Soap11 or JSON. This defines how Spyne will parse the input HTTP request to determine a) what API call needs to be made, and b) what input data should be parsed/passed to the Service.
      • An output protocol.This determines the format/protocol that the API call should return to the callee. This determines how Spyne will serialize objects that the Service returns.
      • A validator. This determines what module will be used to verify valid input data - for example, lxml for WSDL/Soap calls.

For more information on Spyne high-level concepts, please see this documentation page.

  • The default defined protocols are as follows:

    • JSON: Input protocol = JSON, Output Protocol = JSON, Validator = Soft validator. Name = json
    • WSDL: Input protocol = Soap1.1, Output Protocol = Soap1.1, Validation = lxml. Name = wsdl
  • For each defined protocol in API_ENABLED_PROTOCOLS, create the relevant protocol instances, and store in an AppConfig property.

  • Within the urls.py for the trackit_api module, iterate over the protocol instances.

    • For each protocol instance, create a standard Django URL pattern.

    This is a combination of the application name according to the AppConfig (e.g. prolib) and the protocol name as above (e.g. wsdl), and is mapped as, for example, ^/prolib/wsdl/. This is also a named URL pattern of the format “prolib_wsdl”. * For the relevant URL, create a Spyne DjangoView to map directly to the defined Spyne services.

14.1.2. Implementing API Services

Whilst knowledge of the API autodiscovery is useful, it is not a requirement to implement API services. An API service can be thought of as a group of one or more RPC methods, to perform specific functionality. Generally it is a Python class, which extends the “DjangoServiceBase” class provided by Spyne, and implement one or more functions. Each function can be mapped directly to an RPC function.

Please note, each Service is not currently namespaced, so if you implement more than one Service within an api.py module, you should ensure the function names are unique across all services. To implement an API service, first create an api.py file within the relevant module, if it does not already exist. Within the api.py file, implement one or more Spyne services, according to the Spyne documentation. An example Hello World service can be found here.

Please note, it is imperative that the rpc decorator be used over the srpc decorator if you wish to gain access to the Django request object.

The Django request object is stored within the Spyne context object, which is passed as the first argument to a service (if the rpc decorator is used to define the service). It can be accessed via the req property of the transport property of the context arguement. For example:

class HelloService(DjangoServiceBase):
    @rpc(Unicode, _returns=Unicode)
    def whoami(context, input_argument):
        django_request = context.transport.req
        return "You are %s (%s)" % (django_request.user, input_argument)

This will be accessible via the URL endpoint determined by the URL mapping as described in the previous section. Depending on the input protocol, the method you use to call it may differ, but as an example, to call via the JSON protocol we could use curl to simulate this. Assuming the module is within the “trackit_api” Django application, and the server is running on port 8000 on localhost, we would use the following CURL method to do so:

curl -s http://localhost:8000/en-us/api/trackit_api/json/ -d \
             '{"whoami": {"input_argument": "Hello!"}}'

The initial object key (“whoami”) determines the function to call within the service, and the associated object determines the parameters to be mapped to the function (e.g. input_argument = “Hello!”). The context argument is automatically provided by Spyne. This provides the following output:

"You are AnonymousUser (Hello!)"

This neatly demonstrates a) the Django request object access, and b) how Spyne automatically serializes the returned Python object into JSON format. You can test WSDL functionality using the Python library suds, via the same method, as follows:

from suds.client import Client
cli = Client("http://localhost:8000/en-us/api/trackit_api/wsdl/")
print(cli.service.whoami)
<suds.client.Method instance at xxx>
print(cli.service.whoami("Tester!"))
You are AnonymousUser (Tester!)

This demonstrates a) the automatic WSDL generation (which can be seen in a browser by navigation to http://localhost:8000/en-us/api/trackit_api/wsdl/), and b) the automatic RPC discovery via suds, and how the results are serialized.