Expression-Scripting Connectors
Expression bindings and jython scripting have only one
connector in the general case, Ignition’s native runScript()
expression function. In Perspective, scripting transforms
can also be tied to expressions. Integration Toolkit adds
several additional connectors.
The objectScript()
and view()
expression functions
invoke the jython interpreter from an Ignition expression
context, with advanced functionality.
The various *VarMap()
expression functions pull jython
mapping objects into binding scopes, with jython-triggerable
binding support.
Scope-Independent Functions
objectScript()
objectScript(expr, [args...])
returns Object
expr |
A string of code suitable as the right-hand-side of a python
assignment statement. If multiple lines, the following lines
are expected to be syntactically suitable as a continuation
of the right-hand-side expression, and/or more top-level
code. Following lines can change the return value by
assigning to __retv again.
|
args |
Optional arguments to be assembled into a Python tuple and placed
in the locals() dictionary of the expression as
args . One-line
expressions may extract specific values with subscripts or slices,
or any part may be passed to library functions as desired. If
omitted, a zero-length args tuple will still be present.
|
In most scopes, expression functions are provided an
InteractionListener
object which is generally the binding itself. A binding
entry will
be placed into the expression’s locals()
dictionary pointing at the
supplied InteractionListener
, or None
. This can be passed into
a library function to permit an advanced function to arbitrarily
trigger a binding refresh.
In Vision scope, and certain others, the binding
typically has a
target
attribute pointing at the GUI component, and a
targetPropertyName
attribute identifying the bound property on
that component. Binding objects often have additional useful
attributes in the various possible scopes.
In Perspective scope, the binding
object typically does not have
a target
attribute, but self
is made available in the locals()
dictionary. Unlike runScript()
in Perspective scope, session
,
page
, and view
are not placed in the locals()
dictionary.
These are typically attributes of self
, though, if needed.
In all scopes, provides a persistent state
dictionary in locals()
,
unique to the expression instance, to carry information from one
execution to the next.
view()
view(selectString, fromDataset, [args...])
returns Dataset
selectString | A string containing Pseudo-SQL describing the operation(s) to perform on the supplied dataset. See the Pseudo-SQL topic below. |
fromDataset | The source dataset providing the implied FROM clause. |
args |
Optional arguments to be assembled into a Python tuple and placed
in the locals() dictionary of the expression as
args . One-line
expressions may extract specific values with subscripts or slices,
or any part may be passed to library functions as desired. If
omitted, a zero-length args tuple will still be present.
|
Process a dataset into a new dataset, using “pseudo-SQL”, with jython
expressions for each column to return, and with optional jython
expressions in Where
, Group By
,
Pivot
, Having
, Order By
, and Limit
clauses. Extra arguments are
converted into an args
tuple for efficient use within the jython
expressions.
Available in all scopes.
In Gateway scopes, the returned dataset is the type returned by
system.dataset.toDataSet()
, typically a
BasicDataset.
In Vision Client
scope and in Designer scope for Vision bindings, the returned dataset
is passed through the toTransient()
function automatically. This can be
reverted by wrapping the view()
function in the nonTransient()
function,
though this is likely not a good idea in these scopes.
Pseudo SQL
The general form of the selectString
argument is as follows:
Select
pyExpression,
pyExpression As ColumnName,
pyExpression As "Column Name" ...
Where pyConditionalExpression
Group By pyExpression, pyExpression ...
Pivot pyExpression For pyStringExpression [In pySequenceExpression]
Having pyConditionalExpression
Order By pyExpression, pyExpression ...
Limit pyIntegerExpression
Within the selectString, the clause keywords are not case sensitive, but everything else is case-sensitive. Whitespace and line breaks outside of quotes collapse to single spaces, so the Pseudo-SQL may be formatted for easy readability. How the column data and intermediate values are used within the pyExpressions varies by clause, as follows:
-
Column expressions that include an
AS
clause are processed verbatim. When noAS
clause is present, and thepyExpression
is a simple munged source column name, the original column name is used as an implied output column name. In that case, if there is also aGroup By
and/orPivot
clause, a[0]
subscript is appended to the expression. -
When a column expression has no
AS
clause, and the expression is not a simple munged column name, the expression is used verbatim. The output column name is then constructed by passing the entire expression through the name munging function. -
When a column expression is simply an asterisk, it is replaced with the complete source dataset column list, with original output column names, and with
[0]
subscripts as above when grouping or pivoting. -
The code extracts the values from each row of the source dataset, assigning them to local variables using the munged column names. If a Where clause is present, it is evaluated immediately, and the row discarded if not True. The
Where
clause may use the munged names of the source columns, objects normally present in the local scope (system.*
, etc.), theargs
andbinding
objects as described for theobjectScript()
expression function, and_r
, the row index in the source dataset. -
If a
Group By
clause is present, for any rows that pass theWhere
condition, the group key is computed next. TheseGroup By
key expressions have access to the same variables as theWhere
clause. A dictionary keyed by group is created. Each entry is a list of lists of row values for that keying tuple. After all source rows are collected into groups, the code iterates through the list of group keys, assigning the lists of row values back into the munged source column names. -
If a
Pivot
clause is present, the column name string expression in theFor
subclause is used as a final grouping key, but just for the pivoted column expression. The optionalIn
subclause may be either a python list or a python tuple. If a tuple, only the given column headings will be used. Rows generating other column names will be discarded. But if a list, all missing entries will be added after the given ones and no rows will be discarded. The pivot value expressions are computed before the other selected output columns, with values from just the inner group. -
The code next computes the output column values using the column expressions in the
Select
clause. If noGroup By
orPivot
clause was used, these column expressions may use anything valid in theWhere
clause. IfGroup By
orPivot
was used, the column expressions must handle the source columns as python lists instead, and may not use_r
. In either case, each computed output value is available to following column expressions using its output column name (munged if necessary). -
If a
Having
clause is present, it is evaluated immediately after the output values are constructed. It may use any variable syntax valid in an output column expression, including the munged output column names and munged pivot column names. If theHaving
clause is true, or not given at all, an output row is constructed from each set of output column values. -
If an
Order By
clause is present, it is evaluated with each constructed output row to produce an ordering tuple. Like theHaving
clause, these expressions may use any variables valid in an output column expression. The output rows are collected in a temporary list with the ordering tuples, sorted, then the final row list is extracted from the temporary list. If theOrder By
clause is omitted, the constructed output rows are added to the final list directly.
If a Limit
clause is present, it is evaluated immediately after the
output values are sorted and the otherwise final list of output rows
generated. The expression must yield an integer, and will take effect
only if positive. The list of rows is available as the variable _rows
,
making it possible to return a percentage of the rows like so:
LIMIT int(0.10 * len(_rows))
Usage Notes
If you need to programmatically create a selectString for this
function, you can get munged column names from your actual column
names with the system.dataset.mungeColumnName()
auxiliary function.
Basically, column names that contain non-alphanumeric characters are
converted to alphanumeric only, using underscores.
To make grouping operations as similar as practical to real SQL
syntax, common aggregate functions are defined in
script.aggregate.*
. These functions
ignore null/None values as is expected in SQL. Since some have names
matching python builtins, they are only imported without prefix within
the local scope of the view()
function. If you use these in a
script module called from view()
, you must import them or use the full
names.
The aggregate functions are not syntactically identical to real
SQL, though, as they can only take a numberList
argument. In real SQL, a
user may place a scalar expression inside the function’s parenthesis,
and the SQL language takes care of the list conversion. In view()
’s
Pseudo-SQL, the user must handle that themselves. Also, the use of
an aggregate function in real SQL will deliver an aggregated result,
even if no Group By clause is present. In this Pseudo-SQL, you must
use Group By 0
to trigger grouping of all rows. (Group By 0
is
implied if Pivot
is used without Group By
.)
Unlike grouping in real SQL, you may not use an output
column expression that matches a Group By
column or expression.
Automation Professionals recommends
using [0]
or [-1]
as a subscript to obtain the first
or last row in a group for those expressions. (This is done
automatically for simple munged column names.)
When in doubt how view()
is handling your code and data, set its
logger to DEBUG, or wrap in the debugMe()
expression function.
This will show you the python whenever it re-compiles, along with
execution times. Looking at the generated python will also show
other variables you may use in your expressions.
mungeColumnName()
This function takes an arbitrary string and produces
a string that is a valid jython identifier. The view()
expression
functiona delegates to
this function when a column name is not itself a valid jython
identifier, and when a jython expression is supplied in view()
’s
Select
clause without an As
clause.
Sequences of invalid characters are replaced with single underscore characters to yield a valid identifier.
Available as both an expression function (ideal for making mapping object keys acceptable to Perspective) and a scripting function.
mungeColumnName(name)
returns String
system.dataset.mungeColumnName(name)
returns String
name | Any arbitrary string to be munged into a valid identifier. |
Available in all scopes.
system.aggregate.*()
system.aggregate.X(numberList)
returns Double
X | One of count , groupConcat ,
max , mean , min ,
popStdDev , stdDev` , or sum .
Functionally identical to the native
expression functions of the same names. |
numberList | A list (sequence) of values to be operated on, with nulls
dropped. If given an empty list, or empty but for nulls, the
aggregate function return value will be None . |
These scripting functions supplement and provide alternatives to the native python statistical functions, ignoring nulls in lists in the SQL fashion, rather than failing, or counting them as zeros.
They are imported with a wildcard into the local scope of every
execution of the view()
expression function, so that Group By
operations can have more natural SQL-like aggregate behaviors
than the same-named jython built-ins provide.
unQualify()
Given an object, traverse its collections, if any, deep copying to
extract values from qualified values at any depth. Intended to
simplify Perspective lists, mappings, lists of mappings, et cetera,
when passed as an argument to runScript()
or objectScript()
.
Mappings are turned into Python dictionaries. The keys are also deep copied. The keys encountered must yield valid hashables.
Iterables are copied into ArrayLists.
unQualify(object)
returns Object
system.util.unQualify(object)
returns PyObject
object | Any object. Mappings, lists, or arrays will be processed
recursively to not have any QualifiedValue objects nested within. |
orderedCopy()
Given an object, traverse its collections, if any, deep copying to
extract values from qualified values at any depth. Intended to
simplify Perspective lists, mappings, lists of mappings, et cetera,
when passed as an argument to runScript()
or objectScript()
.
Mappings are turned into ordered Python dictionaries with stringified keys.
Iterables are reordered by the stringified content of the value indicated by the given list key, if the list key is not null/None. The list key will be “name” if omitted.
Both ordering operations use a non-case-sensitive AlphaNumeric “human-friendly” algorithm.
Iterables that are not entirely dictionaries, or do not have any any inner keys for the list key, will be deep copied in their original order.
orderedCopy(object [, listKey])
returns Object
system.util.orderedCopy(object [, listKey])
returns PyObject
object | Any object. Mappings, lists, or arrays will be processed
recursively to not have any QualifiedValue objects nested within. |
listKey | Any string, or java null/python None. If omitted, "name" is used. |
alNumCompare()
Supplies an AlphaNumeric Comparator, functionally similar to JideSoft’s AlphaNumeric
comparator, but with two critical additional features:
-
Implements
Serializable
, so can be used in objects exchanged by RPC or traversing the Gateway Network. -
Accepts any java objects in comparisons and stringifies as needed.
system.util.alNumCompare(cased)
returns Comparable<?>
cased | Boolean. Indicates whether to the comparator should be case-sensitive or not. Supply `True` for the case-sensitive version. |
VarMap
This class, exposed as system.util.VarMap
, is the basis for the
BindableVarMap
objects below. As an implementation of Java’s
ConcurrentHashMap<String, Object>
, it is automatically wrapped
by Jython with all of the ordinary dictionary methods and properties,
while retaining its Java methods. But its Jython wrapper is further
enhanced to divert attribute access to dictionary keys (excluding
writes to attributes that start with an underscore). And attribute
reads that match no dictionary key return None
instead of raising
an AttributeError
.
This class is intended to supercede the shared.util.SmartMap
class
that Automation Professionals has published in various places. That
class was inspired by Peter Norvig’s “bag of properties” recommendations
in his Python Infrequently Asked Questions.
This implementation has the obvious performance advantages of pure
Java, but is also safe to place in persistent JVM locations, including
in the globalVarMap()
below, just like native Jython dictionaries and
other native Jython objects. It is also serializable, as long as all of
its contents are serializable, so can be sent back and forth between
Gateway, Designer, and Vision Client scopes freely.
Since it isn’t defined in a project library script, it is safe to import for an abbreviated form:
from system.util import VarMap
# or
from com.automation_pros.simaids.persist import VarMap
globalVarMap()
This expression function and the matching scripting function
(as system.util.globalVarMap()
)
return a unique, persistent mapping object associated with a given
string key. Once created, the mapping object will exist until the JVM
exits. There is no provision for deleting one of these mapping objects
once created. (Though they can be cleared.) There is no provision for
enumerating existing keys (deliberately). The string key should be a
constant associated with the specific application purpose.
Warning! Misuse of these functions will leak memory
and can crash your system.
globalVarMap(key)
returns BindableVarMap
in an expression.
system.util.globalVarMap(key)
returns BindableVarMap
in a script.
key | A constant string uniquely identifying a JVM global dictionary-like object. |
This function is available in all scopes.
The BindableVarMap
return value is a subclass VarMap
, described
above, which gives it all of the methods of a ConcurrentHashMap
along
with all of a Jython dictionary’s methods and the attribute access
to the dictionary content. It has these further enhancements:
-
Can weakly attach to any InteractionListener via its
.bind()
method. This method is automatically invoked by the expression function when in a context that provides one. -
Can trigger any
InteractionListener
that is attached (and has not been garbage collected) via its.refresh()
method. That is, a script that retrieves one of theseBindableVarMap
instances can cause expressions that use the same key to re-evaluate. Something like this:
bvm = system.util.globalVarMap("someKey")
bvm.someAttr = "new value to distribute"
bvm['some non-identifier key'] = "some other new value"
bvm.refresh()
Side note: this functionality is intended to replace Automation
Professionals’ system.util.persistent()
scripting function that
has been available in the “Life Cycle” module for Ignition v7.9 and
beyond. That module was created to solve some problems with
system.util.getGlobals()
introduced in the middle of the v7.9
series and continuing into v8+, but also to avoid the overloaded
use of system.util.getGlobals()
for certain legacy-scoped gateway
events. globalVarMap()
avoids all of those problems and
adds attribute handling and binding to the mix.
Perspective-Related Functions
sessionVarMap()
This expression function and the various overloaded scripting functions
(as system.perspective.sessionVarMap(...)
)
return a unique mapping object associated with a Perspective session.
The mapping object destroys itself when the associated Perspective
session is garbage collected.
Like globalVarMap
, the refresh()
method may be used from jython
to update any UI bindings attached to the mapping object.
sessionVarMap()
returns BindableVarMap
in an expression.
system.perspective.sessionVarMap()
returns BindableVarMap
in a script.
Returns the mapping object for the current session.
Available only in Perspective scope.
system.perspective.sessionVarMap(reference)
returns BindableVarMap
in a script.
reference | Can be a Perspective session object, a Perspective page or view object from a session, or the UUID of a Perspective session. |
Returns the mapping object attached to or associated with the session of the given reference.
Available in Perspective and Gateway scopes.
pageVarMap()
This expression function and the various overloaded scripting functions
(as system.perspective.pageVarMap(...)
)
return a unique mapping object associated with a Perspective page, except
in the designer, where it returns the sessionVarMap.
The mapping object destroys itself when the associated Perspective
object is garbage collected.
Like globalVarMap
, the refresh()
method may be used from jython
to update any UI bindings attached to the mapping object.
The designer environment doesn’t allow navigation, and treats each open view
as if in a page by itself. This would make it impossible to test pageVarMap()
using views that are supposed to live together in a page, except that this
function falls back to the sessionVarMap()
in a designer session. To avoid
clashes in the designer, use different dictionary keys in pages versus sessions.
pageVarMap()
returns BindableVarMap
in an expression.
system.perspective.pageVarMap()
returns BindableVarMap
in a script.
Returns the mapping object for the current page or designer session.
Available only in Perspective Page or View scope.
system.perspective.pageVarMap(pageId)
returns BindableVarMap
in a script.
pageId | A string page ID that identifies a specific page scope in the current Perspective session. |
Returns the mapping object attached to or associated with the specified page or designer session.
Available in Perspective scope.
system.perspective.pageVarMap(reference)
returns BindableVarMap
in a script.
reference | Can be a Perspective page or view object from a session. |
Returns the mapping object attached to or associated with the page or designer session of the given reference.
Available in Perspective and Gateway scopes.
system.perspective.pageVarMap(reference, pageId)
returns BindableVarMap
in a script.
reference | Can be a Perspective session object, a Perspective page or view object from a session, or the UUID of a Perspective session. |
pageId | A string page ID that identifies a specific page scope in the referenced Perspective session. |
Returns the mapping object attached to or associated with the specified page or designer session of the session identified by the given reference.
Available in Perspective and Gateway scopes.
viewVarMap()
This expression function and the various overloaded scripting functions
(as system.perspective.viewVarMap(...)
)
return a unique mapping object associated with a Perspective view.
The mapping object destroys itself when the associated Perspective
view is garbage collected.
Like globalVarMap
, the refresh()
method may be used from jython
to update any UI bindings attached to the mapping object.
viewVarMap()
returns BindableVarMap
in an expression.
system.perspective.viewVarMap()
returns BindableVarMap
in a script.
Returns the mapping object for the current view.
Available only in Perspective View scope.
system.perspective.viewVarMap(viewId)
returns BindableVarMap
in a script.
viewId | A string view ID that identifies a specific view scope in the current Perspective session and page. |
Returns the mapping object attached to or associated with the specified view in the current page.
Available in Perspective Page or View scope.
system.perspective.viewVarMap(pageId, viewId)
returns BindableVarMap
in a script.
pageId | A string page ID that identifies a specific page scope in the current Perspective session. |
viewId | A string view ID that identifies a specific view scope in the current Perspective session and specifiedpage. |
Returns the mapping object attached to or associated with the specified view.
Available in Perspective scope.
system.perspective.viewVarMap(reference)
returns BindableVarMap
in a script.
reference | Can be a Perspective view or view-linked object from a session. |
Returns the mapping object attached to or associated with the view of the given reference.
Available in Perspective and Gateway scopes.
system.perspective.viewVarMap(reference, viewId)
returns BindableVarMap
in a script.
reference | Can be a Perspective page or view or view-linked object from a session. |
viewId | A string view ID that identifies a specific view scope in the referenced Perspective session and page. |
Returns the mapping object attached to or associated with the specified view of the page identified by the given reference.
Available in Perspective and Gateway scopes.
system.perspective.viewVarMap(reference, pageId, viewId)
returns BindableVarMap
in a script.
reference | Can be a Perspective page or view or view-linked object from a session. |
pageId | A string page ID that identifies a specific page scope in the referenced Perspective session. |
viewId | A string view ID that identifies a specific view scope in the referenced Perspective session and page. |
Returns the mapping object attached to or associated with the specified view of the session identified by the given reference and specified page of the session.
Available in Perspective and Gateway scopes.