Codementor Events

Plyer: Platform independent compatibility layer

Published Mar 20, 2017Last updated Jun 10, 2018
Plyer: Platform independent compatibility layer

Plyer is an open source library to access features commonly found in various platforms via python. It is under heavy development with current version being 1.3.0 and provides support for Windows, MacOX, Linux, Android and iOS.


Content

  • History of plyer
  • How to use it?
  • An example with kivy
  • How plyer works?
  • Internal working
  • Contributing.
  • Conclusion.

History of plyer.

Kivy organisation started plyer project as a need to get access of various platform features since kivy organisation provides the framework to build cross-platform apps and softwares using python.

For example, In order to trigger the phone calling interface in the mobile devices, one needs to access the libraries provided by the respective platform in the native language itself.

For android one needs to get android.content.Intent and android.net.Uri and start a new activity with intent action Intent.ACTION_CALL and Intent.setData as telephone number to trigger a call.

For iOS one needs to get UIApplication and NSURL with tel(telephone number) as an object_str to trigger a call.

But how to do the same using python? To tackle this problem kivy started 3 projects:
PyJnius, PyObjus and Plyer.

Pyjnius: A Python module to access Java classes as Python classes using JNI.

Pyobjus: Python module for accessing Objective-C classes as Python classes using Objective-C runtime reflection.

Plyer: A platform-independent api to use features commonly found on various platforms in Python like Accelerometer, Calling, Text to speech, Wifi, Barometer and many more.


How to use plyer?

Using plyer is super easy.

Get accelerometer readings::

>>> from plyer import accelerometer
>>> accelerometer.enable()
>>> accelerometer.acceleration
(-10.048464775085449, 6.825869083404541, 7.7260890007019043)

Get battery status::

>>> from plyer import battery
>>> battery.status 
{'percentage': 82.0, 'isCharging': False}

Turn on flash::

>>> from plyer import flash
>>> flash.on()

I highly recommend you to try Kivy Remote Shell to see these examples work on a real android device. One important point to stress upon is that Plyer is independent of Kivy and can work as a separate entity.


Example with kivy.

Text to Speech converter. Just copy paste the following code and make sure you have kivy installed with version greater than 1.8.0.

Create a main.py file.

import kivy
kivy.require('1.8.0')

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup

from plyer import tts


class Text2SpeechDemo(BoxLayout):
    def do_read(self):
        try:
            tts.speak(self.ids.notification_text.text)
        except NotImplementedError:
            popup = ErrorPopup()
            popup.open()


class Text2SpeechDemoApp(App):
    def build(self):
        return Text2SpeechDemo()

    def on_pause(self):
        return True


class ErrorPopup(Popup):
    pass

if __name__ == '__main__':
    Text2SpeechDemoApp().run()

Create a text2speechdemo.kv file. [name for .kv file should be same]

<Text2SpeechDemo>:
    BoxLayout:
        orientation: 'vertical'
        padding: 20

        TextInput:
            id: notification_text
            text: 'Put message here'
        
        Button:
            text: 'Read'
            size_hint_y: 0.2
            on_press: root.do_read()

<ErrorPopup>:
    size_hint: .7, .4
    title: "Error"
    
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        spacing: 20

        Label:
            size_hint_y: 0.4
            text: "This feature has not yet been implemented in Plyer."
        Button:
            text: 'Dismiss'
            size_hint_y: 0.4
            on_press: root.dismiss()

Run the application using python main.py in any of the supported platforms listed in the introduction and use this awesome feature.


How plyer works?

Plyer tries not to reinvent the wheel, and calls for external libraries to implement the api in the easiest way, depending on the current platform.

  • On Android (python-for-android), pyjnius is used.
  • On iOS (kivy-ios), pyobjus is used
  • On windows/mac/linux, commonly found libraries and programs are used.

Let's look at the calling feature implementation for android platform. One thing to notice is this line of code from jnius import autoclass where jnius is PyJnius and autoclass is a method that returns a Python class see code.

from jnius import autoclass
from plyer.facades import Call
from plyer.platforms.android import activity

Intent = autoclass('android.content.Intent')
uri = autoclass('android.net.Uri')


class AndroidCall(Call):

   def _makecall(self, **kwargs):
       intent = Intent(Intent.ACTION_CALL)
       tel = kwargs.get('tel')
       intent.setData(uri.parse("tel:{}".format(tel)))
       activity.startActivity(intent)

   def _dialcall(self, **kwargs):
       intent_ = Intent(Intent.ACTION_DIAL)
       activity.startActivity(intent_)


def instance():
   return AndroidCall()

Using the above code to trigger calls.

>>> from plyer import call
>>> call.makecall('9997654321')

Internal working.

This sounds cool, how does plyer recognises the platform and uses only those files which are needed?

To understand this you need to dive into the codebase of plyer, first let us look at the file structure of plyer.

- plyer
    - facades
        - __init__.py
        - ... abstract classes of each feature (https://www.python.org/dev/peps/pep-3119/)
        way to abstract classes.
        
    - platforms # Implementations.
        - android/
        - ios/
        - linux/
        - macosx/
        - win/
        - __init__.py
    
    - __init__.py
    - compat.py
    - utils.py
- other files.

Let's take a look at one example of accelerometer and see how it works.

When we do from plyer import accelerometer, since it's a module it goes to __init__.py (link) file where the real magic happens.

# __init__.py file.
from plyer import facades
from plyer.utils import Proxy

#: Accelerometer proxy to :class:`plyer.facades.Accelerometer`
accelerometer = Proxy('accelerometer', facades.Accelerometer)

#other features...

This file creates a proxy for each feature available in facades/__init__.py

# utils.py file.
class Proxy(object):
    ...
    def __init__(self, name, facade):
       ...
   
    def _ensure_obj(self):
      ...
          name = object.__getattribute__(self, '_name')
          module = 'plyer.platforms.{}.{}'.format(
              platform, name)
          mod = __import__(module, fromlist='.')
          obj = mod.instance()

where platform is an object of Platform class which is responsible to detect the platform name [Platform class internally usesfrom sys import platform to get the platform's name.] and name is the feature's name(accelerometer).
mod is the module and instance is the method which returns the class defined in the accelerometer.py file under each platform's implementation.

The path: Goes to __init__.py -> accelerometer = Proxy('accelerometer', facades.Accelerometer) -> Goes to Proxy class -> _ensure_obj gets called -> object obj becomes the object of the class from plyer.platforms.android.accelerometer -> ready to import.


Contributing

Project Plyer is open to contributions visit https://github.com/kivy/plyer#contributing for more info.

Conclusion

Thank you for reading this post — I hope you found this helpful. You can find me on GitHub, LinkedIn and CodeMentor. If you have any questions, feel free to reach out to me!
More posts:

Discover and read more posts from Kuldeep
get started
post commentsBe the first to share your opinion
Show more replies