"""
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
<http://docs.zope.org/zope.interface/README.html>`_ component might be
useful: it's definitely worth a read to help make sense of how the
Config Tool uses it.
"""
#
# Copyright Qualcomm Technologies Inc, 2019.
# All Rights Reserved
#
# Python imports
import logging
import pathlib
import sys
from typing import Tuple, List
import pkg_resources
# Third-party imports
from zope.component import queryMultiAdapter
from zope.configuration.xmlconfig import xmlconfig
[docs]def make_registered_instance(iface, **kwargs):
"""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.
"""
obj = queryMultiAdapter((), iface)
for key, val in kwargs.items():
setattr(obj, key, val)
return obj
[docs]def make_registered_named_instance(iface, name, **kwargs):
"""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.
"""
obj = queryMultiAdapter((), iface, name)
if not obj:
return None
for key, val in kwargs.items():
setattr(obj, key, val)
return obj
[docs]def load_components() -> None:
"""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 :py:mod:`pkg_resources` module to
find all the installed packages. It returns a list of
:py:obj:`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
:py:mod:`pyconfigtool` is put at the beginning of the list so that
any other installed packages can override definitions made in the
default :py:mod:`pyconfigtool` package.
It then asks the :py:mod:`zope.configuration` package to use these
ZCML files to configure utilities, adapters and subscribers using
the :py:mod:`zope.interface` mechanisms.
"""
if getattr(sys, 'frozen', False):
conf_file = pathlib.Path(sys.executable).parent / 'pyconfigtool' / 'configure.zcml'
logging.debug("Loading ZCML file: %s", conf_file)
with conf_file.open('r') as ifile:
xmlconfig(ifile)
else:
entry_points = []
for package in iter(pkg_resources.working_set):
entries = package.get_entry_map('config_tool_plugin')
for entry in entries.values():
if entry.module_name == 'pyconfigtool.main' and entry_points:
entry_points.insert(0, entry)
else:
entry_points.append(entry)
for entry_point in entry_points:
logging.debug("Get config file from package '%s'.", entry_point.module_name)
try:
entry_point = entry_point.load()
conf_file = entry_point() # call the entry-point function
if conf_file:
logging.debug("Loading ZCML file: '%s'.", conf_file)
with open(conf_file, 'r') as ifile:
xmlconfig(ifile)
# we have no idea what exception a plugin may throw
except Exception as exc: # pylint:disable=broad-except
logging.error("%s: %s", type(exc).__name__, str(exc))
logging.warning("Failed to load package '%s'", entry_point.module_name)
[docs]def clear_components() -> None:
"""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.
"""
from zope.configuration.xmlconfig import _clearContext
_clearContext()
[docs]def list_components() -> List[Tuple[str, str]]:
"""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.
:return: a list of two-tuples: where the tuple contains the name of
the Python package and its version.
"""
packages = list(pkg_resources.working_set)
plugin_packages = [(e.project_name, e.version) for e in packages \
if e.get_entry_map('config_tool_plugin') and \
e.project_name != 'pyconfigtool']
return plugin_packages
[docs]def list_packages() -> List[Tuple[str, str]]:
"""This method finds all the packages installed into this virtualenv,
excluding the ones that look like config tool plugins.
:return: a list of two-tuples: where the tuples contain the name
of the Python package and its version.
"""
packages = list(pkg_resources.working_set)
packages = [(e.project_name, e.version) for e in packages \
if not e.get_entry_map('config_tool_plugin')]
return packages