Add Plugin Python API

Overview

Plugin APIs play a big part in the design and philosophy behind Peek.

Peeks philosophy is to create many small plugins that each do one job and do it well.

The idea being, once the plugin is written, you can leverage the functionality of that plugin with out worrying about the internal workings of it.

Anther plugin benefit is to have many smaller code bases. It’s easier for a rouge edit to be introduced into existing code with out strict review procedures. Separate code bases makes this impossible.

Same Service APIs Only

Plugins can only use the APIs of other plugins on the same service.

For example, the code from peek_plugin_one that runs on the Server service can only use the API published by the code in peek_plugin_two that runs on the Server service.

What are APIs

An API is an application programming interface, in python side Peek terms, it’s an exposed abstract class or classes that other plugins can import and use. By “exposed” we mean, anything not under the “_private” package.

The Peek platform provides a method to grab a reference to another plugins exposed API object. The plugin grabbing another plugins API object reference can then call methods directly on it.

The ABC (Abstract Base Class) is purely for documentation purposes, and allows the real implementation to be hidden in the _private package.

In this example, we’re going to expose an API for the Server service in the peek_plugin_tutorial plugin.

We’ll then get the API for the peek_plugin_active_task plugin and create a task.

../_images/LearnPythonPluginApi_ActiveTaskExample.png

Setup Server API

In this section, we define an API on the Peek Server service for the peek_plugin_tutorial plugin.

Add File DoSomethingTuple.py

File DoSomethingTuple.py defines a public tuple that will be returned from the API.


Create the file peek_plugin_tutorial/tuples/DoSomethingTuple.py and populate it with the following contents.

from peek_plugin_tutorial._private.PluginNames import tutorialTuplePrefix
from vortex.Tuple import Tuple, addTupleType, TupleField


@addTupleType
class DoSomethingTuple(Tuple):
    """ Do Something Tuple

    This tuple is publicly exposed and will be the result of the doSomething api call.
    """
    __tupleType__ = tutorialTuplePrefix + 'DoSomethingTuple'

    #:  The result of the doSomething
    result = TupleField(defaultValue=dict)

Edit File tuples.__init__.py

File tuples.__init__.py will be modified to load the tuple when the plugin loads.


Create the file peek_plugin_tutorial/tuples/__init__.py and append the following lines to the def loadPublicTuples(): method.

from . import DoSomethingTuple
DoSomethingTuple.__unused = False

Add Package server

Have you ever wondered why everything so far has been under the _private package? It’s about to make more sense.

The peek_plugin_tutorial.server python package will contain the exposed API abstract classes.


Create the peek_plugin_tutorial/server package, with the commands

mkdir peek_plugin_tutorial/server
touch peek_plugin_tutorial/server/__init__.py

Add File TutorialApiABC.py

File TutorialApiABC.py defines the interface of the API, including what should be detailed docstrings. It doesn’t contain any implementation.


Create the file peek_plugin_tutorial/server/TutorialApi.py and populate it with the following contents.

from abc import ABCMeta, abstractmethod

from peek_plugin_tutorial.tuples.DoSomethingTuple import DoSomethingTuple


class TutorialApiABC(metaclass=ABCMeta):

    @abstractmethod
    def doSomethingGood(self, somethingsDescription:str) -> DoSomethingTuple:
        """ Add a New Task

        Add a new task to the users device.

        :param somethingsDescription: An arbitrary string
        :return: The computed result contained in a DoSomethingTuple tuple

        """

Add File TutorialApi.py

File TutorialApi.py is the implementation of the API. An insance of this class will be passed to other APIs when they ask for it.


Create the file peek_plugin_tutorial/_private/server/TutorialApi.py and populate it with the following contents.

from peek_plugin_tutorial._private.server.controller.MainController import MainController
from peek_plugin_tutorial.server.TutorialApiABC import TutorialApiABC
from peek_plugin_tutorial.tuples.DoSomethingTuple import DoSomethingTuple


class TutorialApi(TutorialApiABC):
    def __init__(self, mainController: MainController):
        self._mainController = mainController

    def doSomethingGood(self, somethingsDescription: str) -> DoSomethingTuple:
        """ Do Something Good

        Add a new task to the users device.

        :param somethingsDescription: An arbitrary string

        """

        # Here we could pass on the request to the self._mainController if we wanted.
        # EG self._mainController.somethingCalled(somethingsDescription)

        return DoSomethingTuple(result="SUCCESS : " + somethingsDescription)


    def shutdown(self):
        pass

Edit File ServerEntryHook.py

We need to update ServerEntryHook.py, to initialise the API object.


Edit the file peek_plugin_tutorial/_private/server/ServerEntryHook.py:

  1. Add this import at the top of the file with the other imports:

    from .TutorialApi import TutorialApi
    
  2. Add this line at the end of the __init__(...): method:

    self._api = None
    
  3. Add this line just before the logger.debug("Started") line at the end of the start() method:

    # Initialise the API object that will be shared with other plugins
    self._api = TutorialApi(mainController)
    self._loadedObjects.append(self._api)
    
  4. Add this line just before the logger.debug("Stopped") line at the end of the stop() method:

    self._api = None
    
  5. Add this method to end of the ServerEntryHook class:

    @property
    def publishedServerApi(self) -> object:
        """ Published Server API
    
        :return  class that implements the API that can be used by other Plugins on this
        platform service.
        """
        return self._api
    

The API is now accessible from other plugins.

Use Server API

In this section we’ll get a reference to the Active Task API and then create a task on the mobile UI.

Note

In order to use this example, you will need to have the peek_plugin_user plugin installed and enabled in both the Client and Server services, via their config.json files.

The user plugin is public, it can be installed with pip install peek-plugin-user.

Note

In order to use this example, you will need to have the peek_plugin_active_task plugin installed and enabled in both the Client and Server services, via their config.json files.

The active task plugin is public, it can be installed with pip install peek-plugin-active-task.

Add File ExampleUseTaskApi.py

File ExampleUseTaskApi.py contains the code that uses the Active Tasks API.


Create the file peek_plugin_tutorial/_private/server/ExampleUseTaskApi.py and populate it with the following contents.

Replace the "userId" with your user id.

import logging
from datetime import datetime

from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks

from peek_plugin_active_task.server.ActiveTaskApiABC import ActiveTaskApiABC, NewTask
from peek_plugin_tutorial._private.server.controller.MainController import MainController

logger = logging.getLogger(__name__)


class ExampleUseTaskApi:
    def __init__(self, mainController: MainController, activeTaskApi: ActiveTaskApiABC):
        self._mainController = mainController
        self._activeTaskApi = activeTaskApi

    def start(self):
        reactor.callLater(1, self.sendTask)
        return self

    @inlineCallbacks
    def sendTask(self):
        # First, create the task
        newTask = NewTask(
            uniqueId=str(datetime.utcnow()),
            userId="userId",  # <----- Set to your user id
            title="A task from tutorial plugin",
            description="Tutorials task description",
            routePath="/peek_plugin_tutorial",
            autoDelete=NewTask.AUTO_DELETE_ON_SELECT,
            overwriteExisting=True,
            notificationRequiredFlags=NewTask.NOTIFY_BY_DEVICE_SOUND
                                      | NewTask.NOTIFY_BY_EMAIL
        )

        # Now send the task via the active tasks API
        yield self._activeTaskApi.addTask(newTask)

        logger.debug("Task Sent")

    def shutdown(self):
        pass

Edit File ServerEntryHook.py

We need to update ServerEntryHook.py, to initialise the example code


Edit the file peek_plugin_tutorial/_private/server/ServerEntryHook.py:

  1. Add this import at the top of the file with the other imports:

    from peek_plugin_active_task.server.ActiveTaskApiABC import ActiveTaskApiABC
    from .ExampleUseTaskApi import ExampleUseTaskApi
    
  2. Add this line just before the logger.debug("Started") line at the end of the start() method:

    # Get a reference for the Active Task
    activeTaskApi = self.platform.getOtherPluginApi("peek_plugin_active_task")
    assert isinstance(activeTaskApi, ActiveTaskApiABC), "Wrong activeTaskApi"
    
    
    # Initialise the example code that will send the test task
    self._loadedObjects.append(
            ExampleUseTaskApi(mainController, activeTaskApi).start()
    )
    

Testing

  1. Open mobile Peek web app
  2. Tap Task icon located in the top right corner
  3. You will see the task in the list