Skip to content

Python Scripts

In addition to executing Python scripts as regular external scripts, NSClient++ can also execute them in-process via the PythonScript module. The benefit is that scripts can call NSClient++’s APIs directly (run checks, listen on channels, register handlers, read/write settings) and keep state between invocations.

To add a Python script as an internal script you need to load the PythonScript module and register the script:

nscp py add --script my_script.py

This produces:

[/modules]
PythonScript = enabled

[/settings/python/scripts]
my_script.py = my_script.py

The key is the script’s alias, and add uses the file name unless you pass --alias my_script.

Managing scripts

Command What it does
nscp py list [--json] [--include-lib] List the files under ${scripts}/python; helpers in a lib folder only with --include-lib
nscp py add --script <file> [--alias <name>] Configure a script and enable the module; --no-config loads it without saving
nscp py add --script <file> --import <path> [--replace] Copy a script into ${scripts}/python first; an existing file is only overwritten with --replace
nscp py show --script <file> Print a script
nscp py delete --script <file> Delete a script and every [/settings/python/scripts] entry that loads it
nscp py install --add <file> / --remove <file> Add or remove a configured script
nscp py execute --script <file> [args...] Run a script’s __main__ (see below)

show and delete only reach ${scripts}/python; a name that resolves anywhere else is refused, and so is a path through a symlinked folder that leads out of it. show reads a symlink only when its target is inside the folder too. delete removes the entry in the folder - a file, or a symlink itself, whatever it points at, dangling or not - and never what a link points to. A deleted script stays loaded until the module reloads. The same operations are available over REST under /api/v2/scripts/py.

Lifecycle functions

A script may define any of three lifecycle functions:

  • __main__ — invoked when the script is run from the command line
  • init — invoked when the script is loaded by the module
  • shutdown — invoked when the module unloads or NSClient++ stops

All three are optional. If a function is missing it is simply skipped.

__main__

__main__ is invoked when the script is run from the command line. Use it for installation, configuration helpers, or anything else an administrator might want to run interactively:

nscp py execute --script my_script.py install --root /tmp

The function receives the command-line arguments as a list of strings:

from NSCP import log

def __main__(args):
    log("My arguments are: %s" % args)

Output:

nscp py execute --script my_script.py install --root /tmp
L     python My arguments are: ['install', '--root', '/tmp']

nscp py execute loads the script as a fresh instance of its own: it calls init(plugin_id, "", "") (no aliases, since nothing configured it), then __main__(args), then shutdown(). The scripts in [/settings/python/scripts] are loaded alongside it as usual.

init

init is invoked when the script is loaded and is where you obtain the values needed to talk back to NSClient++. It receives three arguments:

  • plugin_id — internal identifier of the plugin instance; required by Registry.get(), Core.get(), and Settings.get()
  • plugin_alias — the alias the PythonScript module is loaded under
  • script_alias — the key the script is registered under in [/settings/<alias>/scripts]
from NSCP import log

def init(plugin_id, plugin_alias, script_alias):
    log("*** plugin_id: %d, plugin_alias: %s, script_alias: %s"
        % (plugin_id, plugin_alias, script_alias))

A typical startup log looks like:

D     python boot python
D     python Prepare python
D     python init python
D     python Adding script: osmc (c:\source\build\x64\dev\scripts\python\osmc.py)
D     python Loading python script: c:\source\build\x64\dev\scripts\python\osmc.py
D     python Lib path: c:\source\build\x64\dev\scripts\python\lib
L     python *** plugin_id: 0, plugin_alias: python, script_alias: my_script

plugin_alias versus script_alias

Consider:

[/modules]
ps1 = PythonScript
ps2 = PythonScript

[/settings/ps1/scripts]
ms1 = my_script.py
ms2 = my_script.py

[/settings/ps2/scripts]
ms1 = my_script.py
ms2 = my_script.py

This loads the same script four times with the following arguments:

plugin_id plugin_alias script_alias
0 ps1 ms1
0 ps1 ms2
1 ps2 ms1
1 ps2 ms2

The plugin_id values are assigned by the agent as it loads modules; only rely on them being the same for every script of one module instance. A module loaded without an alias (PythonScript = enabled) passes python as plugin_alias.

If a script may legitimately be loaded multiple times, store its configuration under a path that includes both aliases, e.g. /settings/<script_name>/<plugin_alias>/<script_alias>/.

Warning

Loading the same script multiple times is discouraged — the CLI does not provide shortcuts for it — but it is supported for complex scenarios.

shutdown

shutdown is invoked when NSClient++ stops, when the PythonScript module is unloaded, and when it is reloaded - a reload calls shutdown on every script and then loads them again, calling init anew. It takes no arguments:

from NSCP import log

def shutdown():
    log("Good bye")

API

The module is imported as NSCP and exposes:

  • Three wrapper classes: Settings, Registry, Core — each obtained via .get(plugin_id)
  • An enum: status — Nagios-style return codes
  • Free functions: log, log_error, log_debug, sleep

Most operations have a “simple” variant that uses plain Python types, and a “raw” variant that takes serialized protobuf messages for full access to the underlying API. Use the simple form unless you need fields the simple form doesn’t expose.

Free functions

log / log_error / log_debug

log(message)
log_error(message)
log_debug(message)

Write a message to the NSClient++ log at the corresponding level (info, error, debug). Messages appear on the command line and in any configured log file.

from NSCP import log, log_error, log_debug

log("Informational message")
log_error("Something went wrong")
log_debug("Internal state: %s" % state)

sleep

sleep(milliseconds)

Sleep for the given number of milliseconds. Use this instead of time.sleep(): Python is single-threaded with respect to the GIL, so a blocking time.sleep() inside an NSClient++ script prevents every other Python callback from running. sleep() releases the GIL while it waits.

from NSCP import sleep, log_debug

log_debug("Waiting for 1 second")
sleep(1000) # Other taska can no execute while we wait
log_debug("It is now one second later")

status enum

Key Value Description
OK 0 Nagios OK
WARNING 1 Nagios warning
CRITICAL 2 Nagios critical
UNKNOWN 3 Nagios unknown

Errors in handlers

A handler that raises does not take the agent down. The traceback goes to the NSClient++ log, and the caller gets an answer it can act on:

Handler Raises, or returns the wrong shape Returns None
simple_function (a check) UNKNOWN with the message Exception in: <command> UNKNOWN with the message None
simple_cmdline the message Exception in: <command>, non-zero exit the message None, non-zero exit
simple_subscription the submission fails the submission succeeds
event, event_pb, metrics logged; the other scripts’ handlers still run ignored

A tuple shorter than documented is accepted: (status.WARNING,) is a warning with an empty message. A script that does not parse, or whose init raises, is logged and skipped; the other scripts load regardless, and whatever init registered before it raised stays registered.

Registry

Registry lets a script publish itself into NSClient++ — registering check commands, command-line commands, channel subscriptions, event handlers, and metrics providers.

Registry.get

Registry.get(plugin_id)

Obtain an instance of Registry bound to the calling plugin. Pass the plugin_id you received in init.

Registry.function

Registry.function(query_name, query_function, description)

Register a check query with a raw, protobuf-based callback. Use simple_function instead unless you need fields it doesn’t expose.

Option Description
query_name The check command name (e.g. check_py_test)
query_function Callback invoked when the query runs
description Help text shown by <command> help and in the WEB UI

The callback receives the command name and a serialized QueryRequestMessage, and must return a 2-tuple (return_code, response_bytes) where response_bytes is a serialized QueryResponseMessage:

def my_function(command, request):
    request_message = plugin_pb2.QueryRequestMessage()
    request_message.ParseFromString(request)
    # ... build response ...
    response_message = plugin_pb2.QueryResponseMessage()
    return (status.OK, response_message.SerializeToString())

Example:

from NSCP import log, Registry, status
import plugin_pb2

def my_function(command, request):
    request_message = plugin_pb2.QueryRequestMessage()
    request_message.ParseFromString(request)
    log('Got command: %s' % request_message.payload[0].command)
    response_message = plugin_pb2.QueryResponseMessage()
    return (status.OK, response_message.SerializeToString())

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.function('check_py_test', my_function, 'This is a sample python function')

Registry.simple_function

Registry.simple_function(query_name, query_function, description)

Register a check query with a simple Python callback — no protobuf required.

Option Description
query_name The check command name
query_function Callback invoked when the query runs
description Help text shown by <command> help

The callback receives the arguments as a list of strings and must return a 3-tuple (status, message, perfdata):

def my_function(args):
    return (status.OK, "This is the message", "'count'=123;200;600")

Example:

from NSCP import log, Registry, status

def my_function(args):
    log('Got arguments: %s' % args)
    return (status.OK, "This is the message", "'count'=123;200;600")

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.simple_function('check_py_test', my_function, 'This is a sample python function')

Registry.cmdline

Registry.cmdline(command_name, function)

Register a command-line command with a protobuf-based callback. Use simple_cmdline unless you need raw access. A command-line command is run with nscp client --module PythonScript --exec <command> [arguments...], or from a script with Core.simple_exec. The module’s own verbs come first, so a command registered under one of their names is never reached: help, execute, python-script, add, install, list, show and delete.

Option Description
command_name The command name to expose
function Callback invoked when the command runs

Callback signature: (command, request) -> (return_code, response_bytes), where request is a serialized ExecuteRequestMessage and response_bytes is a serialized ExecuteResponseMessage.

from NSCP import log, Registry, status
import plugin_pb2

def my_function(command, request):
    request_message = plugin_pb2.ExecuteRequestMessage()
    request_message.ParseFromString(request)
    log('Got command: %s' % request_message.payload[0].command)
    response_message = plugin_pb2.ExecuteResponseMessage()
    return (0, response_message.SerializeToString())

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.cmdline('do_foo', my_function)

Registry.simple_cmdline

Registry.simple_cmdline(command_name, function)

Register a command-line command with a simple callback. The callback receives the arguments as a list of strings and returns a 2-tuple (exit_code, message). Run it with:

nscp client --module PythonScript --exec do_something first second
def my_function(args):
    return (0, "This is the message")

Example:

from NSCP import log, Registry

def my_function(args):
    log('Got arguments: %s' % args)
    return (1, "Everything is awesome")

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.simple_cmdline('do_something', my_function)

Registry.subscription

Registry.subscription(channel, function)

Subscribe to a channel (think passive check submissions) with a protobuf-based handler. Use simple_subscription unless you need raw access.

Option Description
channel The channel name to subscribe to
function Callback invoked when a message arrives

Callback signature: (channel, message) -> (success_bool, response_bytes). message is a memoryview over a serialized SubmitRequestMessage.

def my_function(channel, message):
    request = plugin_pb2.SubmitRequestMessage()
    request.ParseFromString(bytes(message))
    # ... process ...
    return (True, b"")

Registry.simple_subscription

Registry.simple_subscription(channel, function)

Subscribe to a channel with a simple per-payload callback.

Option Description
channel The channel name to subscribe to
function Callback invoked per submitted check

Callback signature: (channel, source, command, status, message, perf) -> bool. Return True (or nothing) to accept the submission; False fails it, and the submitter sees the error. source is the system the result is about: the result’s own source when its producer set one, otherwise the source the submission names (for example check_and_forward’s source=). It is empty when neither does.

Example — suppress repeated NSCA submissions:

from NSCP import Registry, Core, status, log

g_last_message = ''
g_plugin_id = 0

def filter_nsca(channel, source, command, code, message, perf):
    global g_last_message, g_plugin_id
    if message != g_last_message:
        g_last_message = message
        log("Sending: %s" % message)
        core = Core.get(g_plugin_id)
        core.simple_submit('NSCA', command, status.CRITICAL,
                           message.encode('utf-8'), perf)
        return True
    else:
        log("Suppressing duplicate message")
        return True

def init(plugin_id, plugin_alias, script_alias):
    global g_plugin_id
    g_plugin_id = plugin_id
    reg = Registry.get(plugin_id)
    reg.simple_subscription('filter_nsca', filter_nsca)

Then point a real-time filter at the synthetic filter_nsca channel instead of NSCA:

[/modules]
PythonScript=enabled
CheckEventLog=enabled
NSCAClient=enabled

[/settings/python/scripts]
filter_nsca=filter_nsca.py

[/settings/eventlog/real-time]
enabled = true

[/settings/eventlog/real-time/filters/login]
log=Security
filter=id=4624
target=filter_nsca
detail syntax=%(id)

Registry.event / Registry.event_pb

Registry.event(event_name, event_function)
Registry.event_pb(event_name, event_function)

Register a handler for a named event. The two variants differ in how the event payload is delivered to the callback:

  • event — payload is delivered as a Python dict. Callback signature: (event_name, data) -> None.
  • event_pb — payload is delivered as bytes: a serialized EventMessage. Callback signature: (event_name, request_bytes). The return value is ignored.

One message can carry several records - a real-time CPU filter sends one per core. An event handler is called once per record, with that record’s data; an event_pb handler once per message, with all of them.

An event is named <source>:<alias>; a real-time CPU filter called high_cpu raises system.cpu:high_cpu.

Example (event):

from NSCP import log, Registry

def on_event(event, data):
    log('Got event: %s with key=%s' % (event, data.get('key')))

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.event('eventlog:login', on_event)

Registry.submit_metrics

Registry.submit_metrics(callable)

Register a function that receives metrics each time NSClient++ publishes a metrics snapshot. Useful for forwarding metrics to a custom backend.

Callback signature: (metrics, "") -> None, where metrics is a flat dict of dotted-name keys to stringified values.

from NSCP import Registry, log

def on_metrics(metrics, _):
    for key, value in metrics.items():
        log("metric %s = %s" % (key, value))

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.submit_metrics(on_metrics)

Registry.fetch_metrics

Registry.fetch_metrics(callable)

Register a function that produces metrics on demand. The callback takes no arguments and returns a dict whose values may be strings, ints, or floats; numeric values are emitted as gauges.

from NSCP import Registry

def my_metrics():
    return {
        "my_script.requests": 42,
        "my_script.latency_ms": 17.3,
        "my_script.status": "ok",
    }

def init(plugin_id, plugin_alias, script_alias):
    reg = Registry.get(plugin_id)
    reg.fetch_metrics(my_metrics)
Describing a metric

A bare number says nothing about what it is, so it scrapes as an anonymous gauge on /api/v2/openmetrics. Return a dict in place of the value to say more:

def my_metrics():
    return {
        "my_script.requests": {
            "value": 42,
            "help": "Requests this script has handled since it started",
            "type": "counter",
        },
        "my_script.latency": {
            "value": 17.3,
            "help": "Time the last request took",
            "unit": "milliseconds",
        },
        "my_script.status": "ok",
    }
Key Required Meaning
value yes The reading, as a string, int or float — exactly what you would have returned on its own. A dict without it is skipped.
help no One line describing what the value is. Becomes the # HELP line.
unit no What the value is measured in (bytes, seconds, percent, …). Becomes the # UNIT line, and the metric name is made to end in it, which OpenMetrics requires of a metric that declares one. Leave it off for a plain count and for a per-second rate.
type no gauge (the default), counter for a value that only grows while the script runs, or unknown. Anything else is reported as a gauge and logged once, so a typo is visible.
labels no A dict of strings that becomes the metric’s labels, the same way the built-in per-core and per-NIC metrics are labelled.

labels is how a script says what one reading is of, rather than what it means:

def my_metrics():
    return {
        "my_script.queue_depth": {
            "value": 42,
            "help": "Messages waiting on the queue",
            "labels": {"queue": "inbound", "region": "eu-west"},
        },
    }

which scrapes as my_script_queue_depth{queue="inbound",region="eu-west"} 42. A label whose name or value is not a string is skipped rather than guessed at, and an empty value is skipped too, since x="" and an absent x are the same series to a scraper. On a string value the labels decide which series of the bundle’s _info family the string lands on.

A unit becomes part of the metric name, so it has to be spelled in [a-zA-Z0-9_]; anything else is rewritten the way a name is (bytes/sec becomes bytes_sec) or dropped if nothing usable is left, and the agent logs it. A counter whose key already ends in _total does not get the suffix twice — the family is named without it and the sample carries it, as the specification requires.

Both forms may be mixed in one dict, and a plain value keeps meaning exactly what it always did: a gauge with no description. A string value, in either form, becomes a label of the bundle’s _info family rather than a sample.

The metadata and the labels are only read by the OpenMetrics endpoint. The JSON endpoints, Graphite, collectd and submit_metrics see the same keys and values as before.

Registry.query

(return_code, response_bytes) = Registry.query(request_bytes)

Issue a raw query against the registry — used to enumerate modules, queries, channels, etc. The request_bytes must be a serialized RegistryRequestMessage; the response is a serialized RegistryResponseMessage.

Core

The Core object exposes operations that interact with the running NSClient++ instance itself — running queries, submitting passive results, loading and reloading modules, expanding paths.

Core.get

Core.get(plugin_id)

Obtain a Core instance bound to the calling plugin.

Core.simple_query

(code, message, perf) = Core.simple_query(query, arguments)

Run a check command by name with a list of argument strings.

Option Description
query Name of the query to run
arguments List of keyword=value argument strings
code Nagios status code
message Resulting message
perf Resulting performance data

A query nobody has registered answers UNKNOWN with the message Unknown command(s): <query>.

from NSCP import Core, log

def init(plugin_id, plugin_alias, script_alias):
    core = Core.get(plugin_id)
    (code, message, perf) = core.simple_query("check_cpu", [])
    log(message)

Core.query

(return_code, response_bytes) = Core.query(command, request_bytes)

Raw protobuf variant of simple_query. request_bytes is a serialized QueryRequestMessage; response_bytes is a serialized QueryResponseMessage.

from NSCP import Core, log
import plugin_pb2

def init(plugin_id, plugin_alias, script_alias):
    request_message = plugin_pb2.QueryRequestMessage()
    # ... populate request_message ...
    core = Core.get(plugin_id)
    (ret, response) = core.query("check_cpu", request_message.SerializeToString())
    response_message = plugin_pb2.QueryResponseMessage()
    response_message.ParseFromString(response)

Core.simple_exec

(return_code, results) = Core.simple_exec(target, command, arguments)

Execute a command-line command (one a script registered via Registry.cmdline / Registry.simple_cmdline, or one a module provides). target picks the module to run it in:

target Runs the command in
a module name or alias that module ("PythonScript", "CheckSystem")
"any" the first module that has the command
"all" or "*" every module that has the command

The name is matched as a substring of each module’s name, so "Python" also reaches PythonScript. Avoid "": it matches every module as though each had been named, and a module asked by name for a command it does not have may answer with an error rather than staying silent. results is a list of strings, one per module that answered.

When nothing could run it - no such module, or no such command in it - return_code is status.UNKNOWN and results holds the reason, Failed to execute <command> on <target>.

from NSCP import Core, log

def init(plugin_id, plugin_alias, script_alias):
    core = Core.get(plugin_id)
    (code, lines) = core.simple_exec("PythonScript", "do_something", ["first"])
    for line in lines:
        log(line)

Core.exec

(return_code, response_bytes) = Core.exec(target, request_bytes)

Raw protobuf variant of simple_exec.

Core.simple_submit

(success, response) = Core.simple_submit(channel, command, code, message, perf)

Submit a passive check result on a channel. When nothing listens on the channel, success is False and response says why (Failed to submit message: <channel>).

Option Description
channel Channel to submit to (e.g. "NSCA", "NRDP")
command Check command name being reported
code status.OK / status.WARNING / status.CRITICAL / status.UNKNOWN
message Message text (bytes or str)
perf Performance data string
from NSCP import Core, status

def init(plugin_id, plugin_alias, script_alias):
    core = Core.get(plugin_id)
    core.simple_submit("NSCA", "check_py", status.OK,
                       "All good".encode("utf-8"), "")

Core.submit

(success, error_message) = Core.submit(channel, request_bytes)

Raw protobuf variant of simple_submit. request_bytes is a serialized SubmitRequestMessage.

Core.reload

Core.reload(module)

Reload the given module by name (e.g. "CheckEventLog"). Pass "service" to reload the entire service.

Called from inside a handler - a check, a subscription, an event - the reload cannot run on that thread, so it is queued and runs shortly after: reload returns True as soon as it is queued, before the module has reloaded, and before anyone knows whether the module exists. A script may reload its own module this way; its shutdown runs, then its init again.

Core.load_module

Core.load_module(module, alias)

Load a module into the running NSClient++ instance. alias lets you load the same module multiple times under different names; pass "" for the default. Returns True once the module is loaded, False if it could not be (for example, no module by that name).

from NSCP import Core

def init(plugin_id, plugin_alias, script_alias):
    core = Core.get(plugin_id)
    core.load_module("CheckEventLog", "")

Core.unload_module

Core.unload_module(module)

Unload a previously loaded module by its module name ("CheckEventLog"); an alias it was loaded under is not looked up. Returns True once it is unloaded, False if no module by that name is loaded. A script cannot unload the module it runs in; that returns False and is logged.

from NSCP import Core

def init(plugin_id, plugin_alias, script_alias):
    core = Core.get(plugin_id)
    core.unload_module("CheckEventLog")

Core.expand_path

path = Core.expand_path(path_expression)

Expand a path containing ${...} placeholders (e.g. ${scripts}, ${modules}, ${base-path}).

from NSCP import Core, log

def init(plugin_id, plugin_alias, script_alias):
    core = Core.get(plugin_id)
    path = core.expand_path("${scripts}/python/myscript.py")
    log('The script path is: %s' % path)

Settings

Settings wraps the configuration store. Read and write are supported. A written value is seen by the next read at once, but is in-memory only until save() is called.

Settings.get

Settings.get(plugin_id)

Obtain a Settings instance bound to the calling plugin.

from NSCP import Settings

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    value = config.get_string("/modules", "PythonScript", "disabled")

Settings.get_section

keys = Settings.get_section(path)

Return the list of keys under a given section.

from NSCP import Settings, log

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    for key in config.get_section("/modules"):
        log("Module: %s" % key)

Settings.get_string / Settings.set_string

value = Settings.get_string(path, key, default_value)
Settings.set_string(path, key, value)

Read or write a string. Writes are not persisted until save() is called.

from NSCP import Settings, log

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    for key in config.get_section("/modules"):
        value = config.get_string("/modules", key, "unknown")
        log("The module %s is %s" % (key, value))
from NSCP import Settings

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    config.set_string("/modules", "PythonScript", "enabled")
    config.save()

Settings.get_bool / Settings.set_bool

value = Settings.get_bool(path, key, default_value)
Settings.set_bool(path, key, value)

Read or write a boolean. true, 1 and yes (in any case) read as True and any other value as False; default_value is returned only when the key is not set.

from NSCP import Settings

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    config.set_bool("/modules", "PythonScript", True)
    config.save()

Settings.get_int / Settings.set_int

value = Settings.get_int(path, key, default_value)
Settings.set_int(path, key, value)

Read or write an integer. A value that is not a number reads as default_value.

from NSCP import Settings, log

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    port = config.get_int("/settings/NRPE/server", "port", -1)
    log("NRPE port is: %d" % port)
    config.set_int("/settings/NRPE/server", "port", 1234)
    config.save()

Settings.save

Settings.save()

Persist any in-memory changes back to the settings store (file or registry, depending on configuration).

Settings.register_path

Settings.register_path(path, title, description)

Register a settings section so it is documented in the generated settings file and visible in the WEB UI.

Settings.register_key

Settings.register_key(path, key, type, title, description, default_value)

Register an individual settings key for documentation and UI purposes. The type argument is accepted for forward-compatibility, but the underlying store currently treats all script-registered keys as strings.

Settings.query

(return_code, response_bytes) = Settings.query(request_bytes)

Issue a raw settings query. request_bytes is a serialized SettingsRequestMessage; the response is a SettingsResponseMessage. Use this when you need operations that the typed helpers above don’t cover (bulk reads, diffs, deletions, etc.).

from NSCP import Settings
import plugin_pb2

def init(plugin_id, plugin_alias, script_alias):
    config = Settings.get(plugin_id)
    request_message = plugin_pb2.SettingsRequestMessage()
    # ... populate request_message ...
    (ret, response) = config.query(request_message.SerializeToString())
    response_message = plugin_pb2.SettingsResponseMessage()
    response_message.ParseFromString(response)