New SDK for Python Adapters

Our family of Adapter Development Kits has been further enriched with the Lightstreamer SDK For Python Adapters, which lets developers write Remote Adapter Sets exploiting the power and flexibility of Python, one of the most widely used general purpose high-level languages.

The new SDK has been developed keeping in mind the same principles and concepts which drove the design and implementation of the SDK for .NET Adapters, the “progenitor” of the successive SDKs for Java Remote Adapters and for Node.js Adapters, all already provided as part of the Lightstreamer distribution. Nevertheless, the Python Adapter SDK must not be considered as a “simple” port to the Python language; on the contrary, it is a brand new implementation, based on the best practices and widespread conventions promoted by the community around the language.

In this post we are going to show a general overview about the development of custom Python Remote Adapter Sets, which should sound quite familiar for those who have already experimented with the others Lightstreamer Adapter SDKs.

Setting Up the Development Environment

The Python Adapter SDK requires Python 3.4 or later and is currently available as a beta version. You can install the package from the PyPi repository, through the pip tool:

$ pip install --pre lightstreamer-adapter

Now, we are ready to write a simple Adapter Set prototype, with the purpose to show the basics of Python Adapters development. 

A Simple Prototype

Let’s start creating a new Python module and call it adapters.py, in which we firstaddthe following code to import the classes required to create our Adapter Set:

from lightstreamer_adapter.server import (DataProviderServer, MetadataProviderServer)
from lightstreamer_adapter.interfaces.data import DataProvider
from lightstreamer_adapter.interfaces.metadata import MetadataProvider

As you can see, the library is structured into three core packages:

  • lightstreamer_adapter.server,  which contains the classes needed to configure and start the Remote Adapters
  • lightstreamer_adapter.interfaces.data, which exposes the ABC (Abstract Base Classes) and the exceptions needed to create and manage a Remote Data Adapter
  • lightstreamer_adapter.interfaces.metadata, which exposes the classes and the exceptions needed to create and manage a Remote Metadata Adapter.

Remote Data Adapter

Creating a Remote Data Adapter is a simple matter of extending the DataProvider class and implementing all its abstract methods: 

class MyDataAdapter(DataProvider):
    """This Remote Data Adapter sample class shows a simple implementation of
    the DataProvider abstract class."""

    def __init__(self):
        # Reference to the provided ItemEventListener instance
        self._listener = None

    def issnapshot_available(self, item_name):
        """Returns True if Snapshot information will be sent for the item_name
        item before the updates."""
        snapshot = False  # May be based on the item_name item
        return snapshot

    def set_listener(self, event_listener):
        """Caches the reference to the provided ItemEventListener instance."""
        self._listener = event_listener

    def subscribe(self, item_name):
        """Invoked to request data for an item. From now on you can start
        sending real time updates for item_name item, through invocations like
        the following:

        self._listener.update(item_name, {'field1': valField1,
                                          'field2': valField2}, False)
        """

    def unsubscribe(self, item_name):
        """Invoked to end a previous request of data for an item. From now on,
        you should stop sending updates for item_name item."""

DataProvider is an Abstract Base Class equivalent to the homonymous interfaces already available in the Java Remote and .NET Adapters, as well as the ItemEventListener interface (actually an ABC too), whose instances are transparently injected through the set_listener method, and then used to send real-time updates or error notifications to the Lightstreamer Kernel.

Remote Metadata Adapter

To create a Remote Metadata Adapter you have to extend the MetadataProvider class, by providing an implementation to those methods you want to customize as per your requirements.
Once again, MetadataProvider is formally equivalent to the ones you can find in the Java Remote and .NET SDKs, even though it supplies a default implementation for all exposed methods, in order to facilitate the coding of new Remote Metadata Adapters.

For the sake of simplicity, in this example we show an extremely simple implementation in which only the notify_user_message method has been overridden:

class MyMetadataAdapter(MetadataProvider):
    """This Remote Metadata Adapter sample class shows a minimal custom
    implementation of the notify_user_message method.
    """

    def notify_user_message(self, user, session_id, message):
        """Invoked to forward a message received by a User"""
        print("Message {} arrived for user {} in the session {}"
              .format(user, session_id, message))

For of all the details relative to the lightstreamer_adapter.interfaces package, take a look at the official API Specification.

Putting It All Together

Now it’s time to put the newly created Remote Adapters “on the road”. The way to do that is by leveraging the DataProviderServer and MetadataProviderServer classes, which have the responsibility to oversee the execution of the Remote Adapter instances and connect them with the Proxy Adapters remotely running on Lightstreamer Server:

if __name__ == "__main__":
    # The host of the Lightstreamer server, to be changed as required.
    LS_SERVER_HOST = 'localhost'

    # The following TCP ports must be set according to the adapters.xml
    # configuration file.

    # The listening request-reply TCP port of the Proxy Metadata Adapter
    META_REQ_REPLY_PORT = 8003
    # The listening request-reply TCP port of the Proxy Data Adapter
    DATA_REQ_REPLY_PORT = 8001
    # The listening notify TCP port of the Proxy Data Adapter
    DATA_NOTIFY_PORT = 8002

    # Create a new MetadataProviderServer instance, passing a new
    # MyMetadataAdpater object and the remote address.
    metadata_provider_server = MetadataProviderServer(MyMetadataAdapter(),
                                                      (LS_SERVER_HOST,
                                                       META_REQ_REPLY_PORT))

    # Start the server instance.
    metadata_provider_server.start()

    # Create a new DataProviderServer instance, passing a new MyDataAdpater
    # object and the remote address
    data_provider_sever = DataProviderServer(MyDataAdapter(),
                                             (LS_SERVER_HOST,
                                              DATA_REQ_REPLY_PORT,
                                              DATA_NOTIFY_PORT))
    # Start the server instance.
    data_provider_sever.start()

These instances require to be configured by supplying at least:

  1. the host address of the Server;
  2. the listening TCP ports for the request-reply and the notification channels of the Proxy Adapters;

but it is also possible to provide further configuration parameters, like “name” (useful for logging purpose) and “keep_alive” interval (to enable a liveness check over the connection): check out the official API Specification for more in-depth details on the lightstreamer_adapter.server module.
DataProviderServer and MetadataProviderServer have very similar duties of their counterparts in .NET and Java, though a slight difference exists, which leads to a further simplification: they contains all the low-level mechanisms needed to manage the communications over TCP/IP, therefore there is no need to manually handle any socket connection to provide the Remote Adapters with. From this perspective, the API supplied by the Python SDK hides a complexity level which, in some cases, might not be desirable.

Before running the module, it’s necessary to properly configure the Lightsteamer Server, by deploying the Remote Adapter Set through the appropriate adapters.xml file, in which the Proxy Adapters need to be configured with the same TCP ports used to setup the connection in the sample code:

<?xml version="1.0"?>
<adapters_conf id="PROXY_PYTHON">
   <metadata_provider>
      <adapter_class>ROBUST_PROXY_FOR_REMOTE_ADAPTER</adapter_class>
      <classloader>log-enabled</classloader>
      <param name="request_reply_port">8003</param>
      <param name="timeout">36000000</param>
   </metadata_provider>
   <data_provider>
      <adapter_class>ROBUST_PROXY_FOR_REMOTE_ADAPTER</adapter_class>
      <classloader>log-enabled</classloader>
      <param name="request_reply_port">8001</param>
      <param name="notify_port">8002</param>
      <param name="timeout">36000000</param>
   </data_provider>
</adapters_conf>

In this case we have chosen the Robust version of the Proxy Adapters, but you can modify it to your liking by using the generic templates you can find both under your Lightstreamer installation (DOCS-SDKs/adapter_remoting_infrastructure/doc) and at the following urls:

To execute the Adapter Sets, simply run the adapters.py module:

$ python adapters.py

Other full code examples, along with complete instructions to deploy and start the Python Adapters against your local Lightstreamer Server installation, are available on GitHub:

Conclusion

With the availability of this new Adapter SDK, we have expanded the set of Lightstreamerenabled platforms to the expressiveness and power of Python, as another remarkable step toward the spread of the Lightstreamer technology across major mainstream languages and platforms, in order to offer wider support for back-end systems integration.

Furthermore, we have decided to release the library as an open-source project on GitHub, so we encourage developers to experiment with it and provide us with any feedback at support@lightstreamer.com.

August 8, 2024
Originally published: April 26, 2016


5 min read

Table of Contents