3.2. Config Tool Plugin APIs

The modules that are described in this section define the APIs to which plugins to the Config Tool can provide concrete implementations.

3.2.1. Background

Unlike Java, Python does not have an interface concept defined in the language. However the ability to specify interfaces indepenent of implementation is a very powerful mechanism which provides a cleaner definition of the published APIs within this application.

The zope.interface package, which was initially devloped by the Zope community quite early on in Python’s evolution, is still a useful framework. It defines an abstract interface using the standard Python class, and encourages a few conventions that make it work. The most important conventions are:

  • the class that defines an interface should have a leading I in its name.
  • the class that defines the interface must derive from the zope.interface.Interface class.
  • abstract method declarations in the interface are defined just as Python methods, but the self parameter must be omitted. The body of the method should just have a pass or, ideally, a docstring describing the semantics of this abstract method.
  • Abstract attributes of the interface must use the zope.interface.Attribute class to define them. See examples below of some of these attributes.

The Zope package provides a framework that enables us to add logic around interfaces and implementations. For example, we can define a concrete factory class that will create an instance of this interface. A concrete implementation of a particular interface should use the decorator (@implementer) to declare to the Zope framework which interface(s) this class is implementing.

A destinction is made between a “utility” interface, and an “adapter” interface. This disctinction is important, and should be understood by reading the documentation on the zope.interface package.

3.2.2. Top Level Utilities

Package to hold interface definitions for elements of the Qualcomm Device Configuration Tool using the zope.interface mechanism.

This module defines a number of utilities methods to make the use of Zope’s interface, component and configuration APIs a little easier.

Some understanding of the zope interface component might be useful: it’s definitely worth a read to help make sense of how the Config Tool uses it.

qct_interfaces.clear_components() → None[source]

This method is unlikely to be used in an application context, but it is useful in running unit tests. It clears out the zope configuration registry.

qct_interfaces.list_components() → List[Tuple[str, str]][source]

This method finds all the plugins for this tool. A plugin is defined as an installed Python package that has config_tool_plugin entry point defined for it.

Returns:a list of two-tuples: where the tuple contains the name of the Python package and its version.
qct_interfaces.list_packages() → List[Tuple[str, str]][source]

This method finds all the packages installed into this virtualenv, excluding the ones that look like config tool plugins.

Returns:a list of two-tuples: where the tuples contain the name of the Python package and its version.
qct_interfaces.load_components() → None[source]

This method finds all the configure.zcml files that have been defined as “data-files” for all the installed packages in the currently running Python environment.

It achieves this by asking the pkg_resources module to find all the installed packages. It returns a list of pkg_resources.Distribution-derived objects, which can be queried to find out if a config_tool_plugin entry point has been declared for that package. The package called pyconfigtool is put at the beginning of the list so that any other installed packages can override definitions made in the default pyconfigtool package.

It then asks the zope.configuration package to use these ZCML files to configure utilities, adapters and subscribers using the zope.interface mechanisms.

qct_interfaces.make_registered_instance(iface, **kwargs)[source]

Create an instance of the interface iface, and sets attributes on that instance. This could be seen as a simple factory method.

This function works in the simple case where an adapter has been defined for the “null” adaption. This would be done in the ZCML like this:

<adapter
    for=""
    factory="packageA.module1.MyImplementation"
    provides="packageB.module2.IMyInterface"
/>

Then in the Python world you can create an instance of MyImplementation with the following:

my_obj = make_registered_instance(packageB.module2.IMyInterface, param1=1, param2=2)

Note that in the definition of the interface, param1 and param2 ought to be defined as zope Attributes of that interface.

qct_interfaces.make_registered_named_instance(iface, name, **kwargs)[source]

Create an instance of the interface iface, registered with the specifed name, and sets attributes on that instance. This could be seen as a simple factory method.

This function works in the simple case where an adapter has been defined for the “null” adaption, but must have been named. This would be done in the ZCML like this:

<adapter
    for=""
    factory="packageA.module1.MyImplementation"
    provides="packageB.module2.IMyInterface"
    name="foo"
/>

Then in the Python world you can create an instance of MyImplementation with the following:

my_obj = make_registered_named_instance(packageB.module2.IMyInterface, "foo",
                                        param1=1, param2=2)

Note that in the definition of the interface, param1 and param2 ought to be defined as zope Attributes of that interface.

3.2.3. Persistence of UI State

The definition of the IPersistState interface.

interface qct_interfaces.persist.IPersistState[source]

The UI state of the Package Manager application is saved using this interface.

This interface will be used as a utility.

restore_config_state()

Return from the persistence store the path to the configuration database, and the URI of the previously connected device.

restore_application_window_state()

Return a two-tuple from the saved state:

  • the top-level window geometry, as a QByteArray.
  • the state of the docking areas, as a QByteArray
save_application_window_state(geometry, dock_state)

Save the top level window geometry and the state of the docking areas.

Parameters:
  • geometry (QByteArray) – The geometry of the main window.
  • dock_state (QByteArray) – The positions and locations of the docking widgets on the main window.
STATE_VERSION = <zope.interface.interface.Attribute object>

A read-only attribute describing the version of the format that is being used to save the state. Changes to this value occur when incompatible format changes occur.

save_config_state(path_to_config_db, current_dev_uri)

Save the current user’s state, which consists of the file path to the current config database, and the URI of the currently chosen device connection.

3.2.4. Application Options

The definition of the IApplicationOptions interface.

interface qct_interfaces.options.IApplicationOptions[source]

The command line arguments to the Package Manager application.

Rather than re-define all the command line options as interface attributes, the options attribute of this class is simply the argparse.Namespace instance.

This interface will be registered as a utility. This means that anwyhere in application code, the command line options to the app can be retreived using this pattern:

from zope.component import getUtility
from pminterfaces.options import IApplicationOptions
...
# e.g. get the verbosity command line argument:
verbosity = getUtility(IApplicationOptions).options.verbosity
options = <zope.interface.interface.Attribute object>

The argparse.Namespace object that is used to parse the command line arguments.

3.2.5. Device Configuration Database

The Config Tool maintains a tree of objects that define what is visible on the user interface. This tree is tradionally referred to as the “model” that the user interface interacts with, usually through “views”. In this application the model objects are actually made persistent simply by deriving them from the Zope Database’s base class for persistence: persistent.Persistent.

This interface defines the methods to create and open one of these persisent object stores.

The definition of the IDeviceDatabase and IDatabaseConverter interfaces.

interface qct_interfaces.database.IDatabaseConverter[source]

This interface is used to mark a conversion tool that converts the database object model to some external format and back again.

This is an utility interface that should be found by running the following:

impl = getUtility(IDatabaseConverter, "converter-name")
conversions_supported()

This method returns information about which direction this converter supports.

Returns:A two-tuple of booleans - the first indicates import support, and the second inicates export support.
export_from_database(root, to_location)

Actually perform the export to a file at the to_location.

extensions_supported()

This method returns the list of filename extensions that this converter will export to and import from. This information might be used to display the files in a file selection dialog with the appropriate list of file extensions.

For example, the YAML converter could return '('.yaml',).

conversion_description(export)

This method returns a human-readable description of the format. The boolean indicates whether the direction of the conversion is for export (True) or import (False).

import_to_database(from_location, root, properties)

Actually perform the import from a file at the from_location.

interface qct_interfaces.database.IDatabaseLocation[source]

This interface defines a marker for a string URI that defines the location of the Device database.

location = <zope.interface.interface.Attribute object>

The URI of where the database should be located

interface qct_interfaces.database.IDeviceDatabase[source]

This adapter defines the interface to creating and managing the ZODB that is used to hold the configuration.

An implementation of this interface can be created by running something like this:

from zope.component import getMultiAdapter
from qct_interfaces.database import IDeviceDatabase
...
dev_database = getMultiAdapter((database_file_location,),
                               IDeviceCapabilities)

The database_file_location object should be an implementation of the IDeviceDatabaseLocation marker interface.

close()

Close the datebase. It’s an error if the database is not open already.

open()

Open the database. If the file doesn’t exist, then a new database is created without a en empty root ConfigDomain in it. If the file does exist, then the file is loaded.

Return type:ConfigDomain object, which is the root domain in the tree.

3.2.6. Global Events

The definition of the IInstallationEvents interface.

interface qct_interfaces.events.IDatabaseEvents[source]

Bases: zope.interface.Interface

This utility defines a number of Signals that get fired when various global actions happen to device configuration databases. The standard usage within application code is something like this:

from zope.component import getUtility
from qct_interfaces.events import IDatabaseEvents
...
sig = getUtility(IDatabaseEvents).datebase_changed_signal()

and then the signal can be fired with:

sig.emit()

or connected to a callback function or method like this:

sig.connect(my_callback_func)
database_attribute_changed_signal()

Returns a signal that gets fired when a config attribute has been changed.

Returns:The QSignal that is fired. The data on the QSignal callback is the attribute that has been changed (an instance of ConfigAttributeBase).
Return type:QSignal(attribute)
device_domain_selected_signal()

Returns a signal that gets fired when a domain item is selected in the Tree view of the database domains.

Returns:The QSignal that is fired. The data on the QSignal callback is the domain object (and instance of ConfigDomain).
Return type:QSignal(domain)

3.2.7. User Interface

3.2.7.1. Themes

The definition of the ITheme interface.

interface qct_interfaces.theme.ITheme[source]

This interface is used to abstract away how and from where theme information is loaded.

This interface is an adapter interface, and expects to be found by adapting an IApplicationOptions and an IPersistState instances. This allows ITheme implementations to choose from either the persisted state or from the command line options which theme to use.

Typical usage to get the real implementation of an ITheme interface will be done something like this:

from zope.component import getMultiAdapter

theme = getMultiAdapter((getUtility(IApplicationOptions),
                         getUtility(IPersistState)), ITheme)
stylesheet_location(area)

Return a string defining the location of a CSS file derived from the area name. This method will be used from WebView-based content, and therefore a path relative to the content itself needs to be returned. The caller needs to determine whether this file actually exists. The implementation of this object will simply apply the rules for its theming strategy for where its CSS files ought to be.

Returns:A string to the relative path of a CSS file.
get_stylesheet(area)

Return a string which is the theme information for a particular area. The areas available are not defined by this interface.

Implementation objects might, for example, implement this by looking for a file with the area name in particular folder.

Returns:A string of (usually) CSS-formatted data, or the empty string if no data is found for the specified area.
get_xml(name)

Some parts of the user interface are rendered using HTML markup. This method fetches markup for a particlarly named part of the user interface. This interface definition does not enumerate what these names are.

The returned XML can contain Kajiki markup so that parts of the XML can be replaced with context data.

Returns:The XML-compliant HTML markup.

3.2.7.2. Attribute Views

The definition of the IAttributeView interface.

interface qct_interfaces.attributeviews.IAttributeView[source]

This adapter defines the interface to a view object that creates and manages the widgets that display and edit the attribute which are contained in a ConfigContainer.

The code that creates instances of this interface is done by runnng something like this:

from qct_interfaces import make_registered_named_instance
from qct_interfaces.attributeviews import IAttributeView
...
check_box_view = make_registered_named_instance(IAttributeView,
                                                'view:qtcheckbox',
                                                config_attribute=attr)
check_box_view.create()

Note the use of the name in this call. The container implementation will look for the attribute view using the most specific to the least specific binding. This means that the code will look for an IAttributeView implmentation registered with the name:

'item:' + attr.attr_key

first (e.g. item:bluetooth_disable_role_switching), and if that doesn’t exist then:

'class:' + attr.attr_type

next (e.g. class:bool), and then finally with the view name defined in the attribute:

'view:' + attr.attr_view

(e.g. view:qtcheckbox).

Plugins can choose to implement their own version of these implementations at whatever level they need to, and so change the presentation layer of the user interface.

create()

This method should construct the widget(s) required for displaying the attribute, which can be accessed by the widget attribute. The caller can then add the widget to the layout of the panel.

config_attribute = <zope.interface.interface.Attribute object>

The ConfigDomain attribute which the widget should be editing.

widget = <zope.interface.interface.Attribute object>

The top-level widget of this view. It is only valid after the create method is called.

self_labelled = <zope.interface.interface.Attribute object>

Boolean value to indicate whether the view contructs its own label.

update()

This method updates the enabled state of the widget(s) associated with the view. This method gets called on all visible attributes whenever one of them is changed.