Command properties

As noted before in End-point commands, command at first tries to process input arguments, calls an associated function and then renders its result. We’ll now introduce properties affecting this process.

Command class properties are written in their bodies and handled by their metaclasses. After being processed, they are removed from class. So they are not accessible as class attributes or from their instances.

Options pre-processing

Influencing properties:

docopt will make a dictionary of options based on usage string such as the one above (Usage string). Options dictionary matching this example looks like this:

{ 'list'       : bool    #  Usage:
, '--all'      : bool    #      %(cmd)s list [--all | --disabled]
, '--disabled' : bool    #      %(cmd)s start <service>
, 'start'      : bool    #
, '<service>'  : str     #  Options:
}                        #      --all       List all services available.
                         #      --disabled  List only disabled services.

Values of this dictionary are passed to an associated function as arguments with names created out of matching keys. Since argument names can not contain characters such as <, >, -, etc., these need to be replaced. Process of renaming of these options can be described by the following pseudo algorithm:

  1. arguments enclosed in brackets are un-surrounded – brackets get removed

    "<service>" -> "service"
    
  2. arguments written in upper case are made lower cased

    "FILE" -> "file"
    
  3. prefix of short and long options made of dashes shall be replaced with single underscore

    "-a"    -> "_a"
    "--all" -> "_all"
    
  4. any non-empty sequence of characters not allowed in python’s identitier shall be replaced with a single underscore

    "_long-option"     -> "_long_option"
    "special--cmd-#2"  -> "special_cmd_2"
    

Points 3 and 4 could be merged into one. But we separate them due to effects of OPT_NO_UNDERSCORES property described below.

See also

Notes in End-point commands for method :py:meth`lmi.scripts.common.command.endpoint.LmiEndPointCommand.transform_options` which is issued before the above algorithm is run.

Treating dashes

Single dash and double dash are special cases of commands.

Double dash in usage string allows to pass option-like argument to a script e.g.:

lmi file show -- --file-prefix-with-double-dash

Without the '--' argument prefixing the file, docopt would throw an error because of --file-prefix-with-double-dash being treated as an unknown option. This way it’s correctly treated as an argument <file> given the usage string:

Usage: %(cmd)s file show [--] <file>

Double dash isn’t passed to an associated function.

Single dash on a command line is commonly used to specify stdout or stdint. For example in the following snippet:

Usage: %(cmd)s file copy (- | <file>) <dest>

'-' stands for standard input which will be read instead of a file if the user wishes to.

Property descriptions

OPT_NO_UNDERSCORES : bool (defaults to False)

Modifies point 3 of options pre-processing. It causes the prefix of dashes to be completely removed with no replacement:

"--long-options" -> "long-option"

This may not be save if there is a command with the same name as the option being removed. Setting this property to True will cause overwriting the command with a value of option. A warning shall be echoed if such a case occurs.

ARG_ARRAY_SUFFIX : str (defaults to "")

Adds additional point (5) to options_transform_algorithm. All repeatable arguments, resulting in a list of items, are renamed to <original_name><suffix> [1]. Repeatable argument in usage string looks like this:

"""
Usage: %(cmd)s start <service> ...
"""

Causing all of the <service> arguments being loaded into a list object.

OWN_USAGE : bool (defaults to False)

Says whether the documentation string of this class is a usage string. Each command in hierarchy can have its own usage string.

This can also be assigned a usage string directly:

class MySubcommand(LmiCheckResult):
    """
    Class doc string.
    """
    OWN_USAGE = "Usage: %(cmd)s --opt1 --opt1 <file> <args> ..."
    EXPECT = 0

But using a boolean value is more readable:

class MySubcommand(LmiCheckResult):
    """
    Usage: %(cmd)s --opt1 --opt1 <file> <args> ...
    """
    OWN_USAGE = True
    EXPECT = 0

Note

Using own usage strings in end-point commands is not recommended. It brings a lot of redundancy and may prove problematic to modify while keeping consistency among hierarchically nested usages.

It’s more readable to put your usage strings in your command multiplexers and put each of them in its own module.

Associating a function

Influencing properties:

When command is invoked, its method execute() will get verified and transformed options as positional and keyword arguments. This method shall pass them to an associated function residing in script library and return its result on completion.

One way to associate a function is to use CALLABLE property. The other is to define very own execute() method like this:

class Lister(command.LmiInstanceLister):
    PROPERTIES = ('Name', "Started", 'Status')

    def execute(self, ns, _all, _disabled, _oneshot):
        kind = 'enabled'
        if _all:
            kind = 'all'
        elif _disabled:
            kind = 'disabled'
        elif _oneshot:
            kind = 'oneshot'
        for service_inst in service.list_services(ns, kind):
            yield service_inst

This may come handy if the application object [2] needs to be accessed or if we need to decide which function to call based on command line options.

Property descriptions

CALLABLE : str (defaults to None)

This is a mandatory option if execute() method is not overriden. It may be a string composed of a full path of module and its callable delimited with ':':

CALLABLE = 'lmi.scripts.service:start'

Causes function start() of 'lmi.scripts.service' module to be associated with command.

Callable may also be assigned directly like this:

from lmi.scripts import service
class Start(command.LmiCheckResult):
    CALLABLE = service.start
    EXPECT = 0

The first variant (by assigning string) comes handy if the particular module of associated function is not yet imported. Thus delaying the import until the point of function’s invocation - if the execution comes to this point at all. In short it speeds up execution of LMI metacommand by reducing number of module imports that are not needed.

Function invocation

Influencing properties:

Property descriptions

NAMESPACE : str (defaults to None)

This property affects the first argument passed to an associated function. Various values have different impact:

Value Value of first argument. Its type
None Same impact as value "root/cimv2" lmi.shell.LMINamespace.LMINamespace
False Raw connection object lmi.shell.LMIConnection.LMIConnection
any path Namespace object with given path lmi.shell.LMINamespace.LMINamespace

This usually won’t need any modification. Sometimes perhaps associated function might want to access more than one namespace, in that case an instance of LMIConnection might prove more useful.

Namespace can also be overriden globally in a configuration file or with an option on command line.

CONNECTION_TIMEOUT : int (defaults to None)
Specifies maximum number of seconds we should wait for broker’s reply. If reached, ConnectionError will be raised. The default value is provided by underlying library [3].

Output rendering

All these options begin with FMT_ which is a shortcut for formatter as they become options to formatter objects. These can be defined not only in end-point commands but also in multiplexers. In the latter case they set the defaults for all their direct and indirect child commands.

Note

These options override configuration settings and command line options. Therefor use them with care.

They are:

FMT_NO_HEADINGS : bool (defaults to False)

Allows to suppress headings (column or row names) in the output.

Note

With LmiLister command it’s preferable to set the COLUMNS property to empty list instead. Otherwise associated function is expected to return column headers as a first row in its result.

FMT_HUMAN_FRIENDLY : bool (defaults to False)
Forces the output to be more pleasant to read by human beings.

Command specific properties

Each command class can have its own specific properties. Let’s take a look on them.

LmiCommandMultiplexer

COMMANDS : dict (mandatory)

Dictionary assigning subcommands to their names listed in usage string. Example follows:

class MyCommand(LmiCommandMultiplexer):
    '''
    My command description.

    Usage: %(cmd)s mycommand (subcmd1 | subcmd2)
    '''
    COMMANDS = {'subcmd1' : Subcmd1, 'subcmd2' : Subcmd2}
    OWN_USAGE = True

Where Subcmd1 and Subcmd2 are some other LmiBaseCommand subclasses. Documentation string must be parseable with docopt.

COMMANDS property will be translated to child_commands() class method by MultiplexerMetaClass.

FALLBACK_COMMAND : lmi.scripts.common.command.endpoint.LmiEndPointCommand

Command class used when no command defined in COMMANDS dictionary is passed on command line.

Take for example this usage string:

"""
Display hardware information.

Usage:
    %(cmd)s [all]
    %(cmd)s system
    %(cmd)s chassis
"""

This suggests there are tree commands defined taking care of listing hardware informations. Entry point definition could look like this:

class Hardware(command.LmiCommandMultiplexer):
    OWN_USAGE = __doc__     # usage string from above
    COMMANDS  = { 'all'     : All
                , 'system'  : System
                , 'chassis' : Chassis
                }
    FALLBACK_COMMAND = All

Without the FALLBACK_COMMAND property, the multiplexer would not handle the case when 'all' argument is omitted as is suggested in the usage string. Adding it to command properties causes this multiplexer to behave exactly as All subcommand in case that no command is given on command line.

LmiSelectCommand properties

Following properties allow to define profile and class requirements for commands.

SELECT : list (mandatory)

Is a list of pairs (condition, command) where condition is an expression in LMIReSpL language. And command is either a string with absolute path to command that shall be loaded or the command class itself.

Small example:

SELECT = [
      ( 'OpenLMI-Hardware < 0.4.2'
      , 'lmi.scripts.hardware.pre042.PreCmd'
      )
    , ('OpenLMI-Hardware >= 0.4.2 & class LMI_Chassis == 0.3.0'
      , HwCmd
      )
]

It says: Let the PreHwCmd command do the job on brokers having openlmi-hardware package older than 0.4.2. Use the HwCmd anywhere else where also the LMI_Chassis CIM class in version 0.3.0 is available.

First matching condition wins and assigned command will be passed all the arguments. If no condition can be satisfied and no default command is set, an exception will be raised.

See also

Definition of LMIReSpL mini-language: parser

DEFAULT : string or reference to command class
Defines fallback command used in case no condition in SELECT can be satisfied.

LmiLister properties

COLUMNS : tuple

Column names. It’s a tuple with name for each column. Each row of data shall then contain the same number of items as this tuple. If omitted, associated function is expected to provide them in the first row of returned list. It’s translated to get_columns() class method.

If set to empty list, no column headers will be printed. Every item of returned list of associated function will be treated as data. Note that setting this to empty list makes the FMT_NO_HEADINGS property redundant.

LmiShowInstance and LmiInstanceLister properties

These two classes expect, as a result of their associated function, an instance or a list of instances of some CIM class. They take care of rendering them to standard output. Thus their properties affect the way how their properties are rendered.

PROPERTIES : tuple

Property names in the same order as the properties shall be listed. Items of this tuple can take multiple forms:

Property Name : str
Will be used for the name of column/property in output table and the same name will be used when obtaining the value from instance. Thus this form may be used only if the property name of instance can appear as a name of column.
(Column Name, Property Name) : (str, str)
This pair allows to render value of property under different name (Column Name).
(Column Name, getter) : (str, callable)
This way allows the value to be arbitrarily computed. The second item is a callable taking one and only argument – the instance of class to be rendered.

Example below shows different ways of rendering attributes for instances of LMI_Service CIM class:

class Show(command.LmiShowInstance):
    CALLABLE = 'lmi.scripts.service:get_instance'
    PROPERTIES = (
            'Name',
            ('Enabled', lambda i: i.EnabledDefault == 2),
            ('Active', 'Started'))

First property will be shown with the same label as the name of property. Second one modifies the value of EnabledDefault from int to bool representing enabled state. The last one uses different label for property name Started.

DYNAMIC_PROPERTIES : bool (defaults to False)

Whether the associated function is expected to return the properties tuple itself. If True, the result of associated function must be in form:

(properties, data)

Where properties have the same inscription and meaning as a PROPERTIES property of class.

Otherwise, only the data is expected.

Note

Both LmiShowInstance and LmiInstanceLister expect different data to be returned. See LmiShowInstance and LmiInstanceLister for more information.

Note

Omitting both PROPERTIES and DYNAMIC_PROPERTIES makes the LmiShowInstance render all attributes of instance. For LmiInstanceLister this is not allowed, either DYNAMIC_PROPERTIES must be True or PROPERTIES must be filled.

LmiCheckResult properties

This command typically does not produce any output if operation succeeds. The operation succeeds if the result of associated function is expected. There are more ways how to say what is an expected result. One way is to use EXPECT property. The other is to provide very own implementation of check_result method.

EXPECT: (mandatory)

Any value can be assigned to this property. This value is then expected to be returned by associated function. Unexpected result is treated as an error.

A callable object assigned here has special meaning. This object must accept exactly two parameters:

  1. options - Dictionary with parsed command line options returned by docopt after being processed by transform_options().
  2. result - Return value of associated function.

If the associated function does not return an expected result, an error such as:

There was 1 error:
host kvm-fedora-20
    0 != 1

will be presented to user which is not much helpful. To improve user experience, the check_result method could be implemented instead. Note the example:

class Update(command.LmiCheckResult):
    ARG_ARRAY_SUFFIX = '_array'

    def check_result(self, options, result):
        """
        :param list result: List of packages successfuly installed
            that were passed as an ``<package_array>`` arguments.
        """
        if options['<package_array>'] != result:
            return (False, ('failed to update packages: %s' %
                    ", ".join( set(options['<package_array>'])
                             - set(result))))
        return True

The execute() method is not listed to make the listing shorter. This command could be used with usage string such as:

%(cmd)s update [--force] [--repoid <repository>] <package> ...

In case of a failure, this would produce output like this one:

$ lmi sw update wt wt-doc unknownpackage
There was 1 error:
host kvm-fedora-20
    failed to update packages: unknownpackage

See also

Docopt home page and its git: http://github.org/docopt/docopt.


[1]Angle brackets here just mark the boundaries of name components. They have nothing to do with arguments.
[2]Application object is accessible through app property of each command instance.
[3]lmiwbem‘s default timeout is 1 minute as of now.