Source code for taurus.cli.alt

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""
This module provides several taurus CLI subcommands (plot, trend, image,...)
that show a widget which may have more than one alternative implementation.
The available registered implementations for each command can be listed with
the `--ls-alt` option. Specific implementations can be selected with the
`--use-alt` option.

These commands also honour the default implementation selection configured in
:mod:`taurus.tauruscustomsettings` via the `*_ALT` variables

The alternative implementations can be registered using the following
entry-point group names:

- `taurus.plot.alt`: the loaded object is expected to be a `TaurusPlot`
  implementation

- `taurus.trend.alt`: the loaded object is expected to be a `TaurusTrend`
  implementation

- `taurus.trend2d.alt`: the loaded object is expected to be a
  `TaurusTrend2dDialog` implementation

- `taurus.image.alt`: the loaded object is expected to be a `TaurusImageDialog`
  implementation


Expected API for alternative implementations
--------------------------------------------

When registering a widget class to be used for these commands, the classes need
to provide the following minimum API:

- For **all** the classes:
  - must be a Qt.QWidget
  - must implement `setModel()` and `getModel()` equivalent to those in
  :class:`taurus.qt.qtgui.base.TaurusBaseComponent`

- For TaurusPlot:
  - should implement `setXAxisMode(mode)` where mode is one of `"n"` or `"t"`
  - should implement `loadConfigFile(name)` equivalent to
  :meth:`taurus.qt.qtcore.configuration.BaseConfigurableClass.loadConfigFile`

- For TaurusTrend:
  - should implement `setXAxisMode(mode)` where mode is one of `"n"` or `"t"`
  - should implement  `loadConfigFile(name)` equivalent to
  :meth:`taurus.qt.qtcore.configuration.BaseConfigurableClass.loadConfigFile`
  - should implement `setMaxDataBufferSize(n)` where `n` is an integer
  - should implement `setForcedReadingPeriod(period)` where `period` is an
  integer

- For TaurusTrend2D:
  - should accept the following keyword arguments in its constructor:

    - stackMode=x_axis_mode where mode is one of `"d"` or `"n"` or `"t"`
    - wintitle=window_name,
    - buffersize=max_buffer_size

- For TaurusImage:
  - should accept the following keyword arguments in its constructor:

    - wintitle=window_name,

  - should implement `setRGBmode(mode)` where mode is one of `"gray"`, `"rgb"`
"""

import sys
import click
import taurus.cli.common
from taurus.core.util.plugin import selectEntryPoints
from taurus import tauruscustomsettings as _ts
from taurus import warning, info
from taurus.qt.qtgui.application import TaurusApplication

__all__ = [
    "x_axis_mode_option",
    "max_buffer_option",
    "forced_read_option",
    "plot_cmd",
    "trend_cmd",
    "trend2d_cmd",
    "image_cmd",
]


EP_GROUP_PLOT = "taurus.plot.alts"
EP_GROUP_TREND = "taurus.trend.alts"
EP_GROUP_TREND2D = "taurus.trend2d.alts"
EP_GROUP_IMAGE = "taurus.image.alts"


def _print_alts(group):
    alts = [ep.name for ep in selectEntryPoints(group)]
    print("Registered alternatives :\n {}\n".format("\n ".join(alts)))


def _load_class_from_group(group, include=(".*",), exclude=()):
    """
    Factory that returns the first available class from the group entry point.
    The selection is done among the classes registered in
    the `group` entry-point, prioritized according to the given
    `include` and `exclude` patterns
    (see :func:`taurus.core.util.plugin.selectEntryPoints`)
    """
    eps = selectEntryPoints(group, include=include, exclude=exclude)
    for ep in eps:
        try:
            return ep.load(), ep.name
        except Exception:
            info("Cannot load %s", ep.name)
    raise ImportError("Could not load any class from {}".format(eps))


[docs]def x_axis_mode_option(choices=("t", "n")): hlp = { "n": "regular axis", "e": "event number", "t": "absolute time/date axis", "d": "delta time axis", } o = click.option( "-x", "--x-axis-mode", "x_axis_mode", type=click.Choice(choices), default=choices[0], show_default=True, help=( "X axis mode: " + ", ".join([k + " for " + hlp[k] for k in choices]) ), ) return o
[docs]def max_buffer_option(default): o = click.option( "-b", "--buffer", "max_buffer_size", type=int, default=default, show_default=True, help=( "Maximum number of values to be stacked " + "(when reached, the oldest values will be " + "discarded)" ), ) return o
forced_read_option = click.option( "-r", "--forced-read", "forced_read_period", type=int, default=-1, metavar="MILLISECONDS", help="force re-reading of the attributes every MILLISECONDS ms", ) @click.command("plot") @taurus.cli.common.models @taurus.cli.common.config_file @x_axis_mode_option(["n", "t"]) @taurus.cli.common.demo @taurus.cli.common.window_name("TaurusPlot") @taurus.cli.common.use_alternative @taurus.cli.common.list_alternatives def plot_cmd( models, config_file, x_axis_mode, demo, window_name, use_alt, ls_alt ): """Shows a plot for the given models""" if ls_alt: _print_alts(EP_GROUP_PLOT) sys.exit(0) if use_alt is None: use_alt = getattr(_ts, "PLOT_ALT", ".*") try: TPlot, epname = _load_class_from_group( EP_GROUP_PLOT, include=[use_alt] ) except Exception: _print_alts(EP_GROUP_PLOT) sys.exit(1) app = TaurusApplication(app_name="taurusplot({})".format(epname)) w = TPlot() w.setWindowTitle(window_name) if demo: models = list(models) models.extend(["eval:rand(100)", "eval:0.5*sqrt(arange(100))"]) try: w.setXAxisMode(x_axis_mode) except Exception as e: warning( 'Could not set X axis mode to "%s" on %s plot. Reason: "%s"', x_axis_mode, epname, e, ) sys.exit(1) if config_file is not None: try: w.loadConfigFile(config_file) except Exception as e: warning( 'Could not load config file "%s" on %s plot. Reason: "%s"', config_file, epname, e, ) sys.exit(1) if models: w.setModel(models) w.show() sys.exit(app.exec_()) @click.command("trend") @taurus.cli.common.models @taurus.cli.common.config_file @x_axis_mode_option(["t", "n"]) @taurus.cli.common.demo @taurus.cli.common.window_name("TaurusTrend") @taurus.cli.common.use_alternative @taurus.cli.common.list_alternatives @max_buffer_option(None) @forced_read_option def trend_cmd( models, config_file, x_axis_mode, demo, window_name, use_alt, ls_alt, max_buffer_size, forced_read_period, ): """Shows a trend for the given models""" # list alternatives option if ls_alt: _print_alts(EP_GROUP_TREND) sys.exit(0) # use alternative if use_alt is None: use_alt = getattr(_ts, "TREND_ALT", ".*") # get the selected alternative try: TTrend, epname = _load_class_from_group( EP_GROUP_TREND, include=[use_alt] ) except Exception: _print_alts(EP_GROUP_TREND) sys.exit(1) app = TaurusApplication(app_name="taurustrend({})".format(epname)) w = TTrend() # window title option w.setWindowTitle(window_name) # demo option if demo: models = list(models) models.extend(["eval:rand()", "eval:1+rand(2)"]) # x axis mode option try: w.setXAxisMode(x_axis_mode) except Exception as e: warning( 'Could not set X axis mode to "%s" on %s plot. Reason: "%s"', x_axis_mode, epname, e, ) sys.exit(1) # configuration file option if config_file is not None: try: w.loadConfigFile(config_file) except Exception as e: warning( 'Could not load config file "%s" on %s plot. Reason: "%s"', config_file, epname, e, ) sys.exit(1) # set models if models: w.setModel(list(models)) # period option if forced_read_period > 0: w.setForcedReadingPeriod(forced_read_period) # max buffer size option if max_buffer_size is not None: try: w.setMaxDataBufferSize(max_buffer_size) except Exception as e: warning( 'Could not set max buffer size on %s trend. Reason: "%s"', epname, e, ) sys.exit(1) w.show() sys.exit(app.exec_()) @click.command("trend2d") @taurus.cli.common.model @x_axis_mode_option(["d", "t", "n", "e"]) @taurus.cli.common.demo @taurus.cli.common.window_name("TaurusTrend2D") @taurus.cli.common.use_alternative @taurus.cli.common.list_alternatives @max_buffer_option(512) def trend2d_cmd( model, x_axis_mode, demo, window_name, use_alt, ls_alt, max_buffer_size ): # list alternatives option if ls_alt: _print_alts(EP_GROUP_TREND2D) sys.exit(0) # use alternative if use_alt is None: use_alt = getattr(_ts, "TREND2D_ALT", ".*") # get the selected alternative try: TTrend2D, epname = _load_class_from_group( EP_GROUP_TREND2D, include=[use_alt] ) except Exception: _print_alts(EP_GROUP_TREND2D) sys.exit(1) app = TaurusApplication(app_name="Taurus Trend 2D ({})".format(epname)) w = TTrend2D( stackMode=x_axis_mode, wintitle=window_name, buffersize=max_buffer_size ) if demo: model = "eval:x=linspace(0,3,40);t=rand();sin(x+t)" if model: w.setModel(model) w.show() sys.exit(app.exec_()) @click.command("image") @taurus.cli.common.model @taurus.cli.common.demo @taurus.cli.common.window_name("TaurusImage") @taurus.cli.common.use_alternative @taurus.cli.common.list_alternatives @click.option( "-c", "--color-mode", "color_mode", type=click.Choice(["gray", "rgb"]), default="gray", show_default=True, help=("Color mode expected from the attribute"), ) def image_cmd(model, demo, window_name, color_mode, use_alt, ls_alt): # list alternatives option if ls_alt: _print_alts(EP_GROUP_IMAGE) sys.exit(0) # use alternative if use_alt is None: use_alt = getattr(_ts, "IMAGE_ALT", ".*") # get the selected alternative try: TImage, epname = _load_class_from_group( EP_GROUP_IMAGE, include=[use_alt] ) except Exception: _print_alts(EP_GROUP_IMAGE) sys.exit(1) app = TaurusApplication(app_name="Taurus Image ({})".format(epname)) rgb_mode = color_mode == "rgb" # TODO: is "-c rgb --demo" doing the right thing?? Check it. if demo: if color_mode == "rgb": model = "eval:randint(0,256,(10,20,3))" else: model = "eval:rand(256,128)" w = TImage(wintitle=window_name) w.setRGBmode(rgb_mode) # set model if model: w.setModel(model) w.show() sys.exit(app.exec_())