Scripts
Ignition runs every script on Jython 2.7. Many mistakes in a script are only discovered when the script runs: on the gateway, in a session, or in the middle of the night when a timer fires. iglint parses each script as Jython does, with the header Ignition wraps around it, and reports these mistakes in the file.
All examples on this page are invented; the messages are iglint's.
Undefined names
Section titled “Undefined names”# plant/orders/code.py (a library module)def total_quantity(rows): for row in rows: total = row["qty"] return totlcode.py:4:9: SCR101 [error] Undefined name `totl`code.py:3:3: SCR103 [warning] Local variable `total` is assigned to but never usedSCR101 knows what Ignition provides without an import: system, the legacy alias fpmi, sys, the project's top-level library packages (also those inherited from a parent project) and the parameters of each script's header (self and event in an event action, value, quality and timestamp in a transform, payload in a message handler). Unlike pylint, it does not report those names, and it applies Python 2.7's scoping rules: list comprehension variables leak into the enclosing scope, generator expressions do not, and names bound in a class body are not visible to its methods.
It stays silent where it cannot be sure: in scopes with from x import * or unqualified exec, in modules that write their own namespace (globals()[...] = ..., setattr(sys.modules[__name__], ...)), inside a try that catches NameError, and in projects whose parent project is not in the workspace.
Errors that only the gateway would show
Section titled “Errors that only the gateway would show”Some code parses but Jython refuses to compile it. The error appears in the gateway log when the script is first used.
def duplicate_argument(a, a): return a
def stop_early(x): if x: breakcode.py:1:27: SCR002 [error] SyntaxError: duplicate argument name found: acode.py:7:3: SCR002 [error] SyntaxError: 'break' outside loopSCR002 reports, with Jython's message, every case Jython's compiler rejects after parsing: return or yield outside a function, break or continue outside a loop, return value in a generator, import * in a nested function, a bare except: that is not last, a misplaced from __future__ import, and more. It also reports scripts that are too large for Jython: a function or module whose bytecode certainly exceeds the 64 KB limit of one Java method ("Module or method too large"), a module with more than 32,767 distinct string literals, or a first line of 100,000 characters (which breaks Jython's encoding detection). iglint reports a size problem only when a lower bound of the generated bytecode, measured on Ignition's Jython, exceeds the limit, so it never reports a script that compiles.
SCR003 reports what Jython compiles with a SyntaxWarning, such as global x after x is already used.
Python 3 syntax
Section titled “Python 3 syntax”Ignition is not Python 3. iglint recognises common Python 3 syntax and says so:
def greet(name): return f"Hello {name}"code.py:2:10: SCR001 [error] SyntaxError: invalid syntax: unexpected string "Hello {name}" (f-strings are Python 3 syntax; use '%' formatting or str.format())A script with a syntax error is not analysed further, so fix SCR001 findings first: other rules may report more once the script parses.
The system.* API
Section titled “The system.* API”The scripting API in iglint was generated from a real Ignition 8.3 gateway: every package, function, overload and parameter, in every scope.
def read_speed(): return system.tag.readBlocking()
def today(): return system.date.nowe()code.py:2:9: SCR202 [error] Call to `system.tag.readBlocking`: missing required argument `tagPaths`code.py:6:9: SCR201 [error] `system.date.nowe` does not exist in Ignition 8.3; did you mean `system.date.now`?| Rule | Reports |
|---|---|
SCR201 |
a system.* member that does not exist (by default only near misses of a real member, since third-party modules add members; all of them with strict) |
SCR202 |
a call whose arguments match no overload |
SCR203 |
a keyword argument the function does not accept |
SCR204 |
a deprecated function, with its replacement (for example system.db.runNamedQuery, which 8.3 replaces with system.db.execQuery and system.db.execUpdate) |
SCR205 |
a function that does not exist where the script runs |
SCR206 |
the 7.x prefixes project., shared. and app., which do not exist in 8.3 |
SCR205 catches scope mistakes that otherwise surface as an AttributeError at run time:
# a Perspective button's onActionPerformed scriptsystem.gui.messageBox("Saved")view.json:154:30: SCR205 [error] `system.gui` is not available in Perspective scripts, which run on the gateway; it exists in the Vision Client and the DesignerIt also reports system.perspective.* functions that need a sessionId when they are called without one from a gateway event, a WebDev handler or a tag script, where there is no current session.
Redefined event handlers
Section titled “Redefined event handlers”Ignition compiles a gateway event file under its canonical header (def handleTimerEvent():), runs the module, then calls the last module-level binding of the handler name with the canonical arguments. A second definition with other parameters means the event never runs:
def handleTimerEvent(): pass
def handleTimerEvent(event): system.util.getLogger("heartbeat").info("tick")handleTimerEvent.py:4:5: SCR104 [warning] Redefinition of unused `handleTimerEvent` from line 1handleTimerEvent.py:4:5: SCR307 [error] `handleTimerEvent` is redefined here with parameters that cannot take no arguments: Ignition runs this last definition, not `def handleTimerEvent():`, and every call fails with a TypeErrorThis behaviour was verified on an 8.3.3 gateway. SCR301 reports event script files whose first line is not the canonical header at all (Ignition drops or re-wraps it).
Wrong argument counts to library functions
Section titled “Wrong argument counts to library functions”Library functions are checked across files: a call is compared with the definition of the function in the project library (including inherited projects).
def handleTimerEvent(): plant.pumps.refresh("North") plant.pumps.refresh("South", True)handleTimerEvent.py:3:2: REF003 [error] `plant.pumps.refresh` accepts at most 1 argument positionally, the call passes 2See References between resources for unknown library members (REF002).
Jython pitfalls
Section titled “Jython pitfalls”Code that runs, but not the way it reads:
def parse_speed(text): try: return float(text) except ValueError, TypeError: return None
def label(name, qty): return "%s: %d of %d" % (name, qty)code.py:4:21: SCR115 [error] `TypeError` is bound to the caught exception, not caught; use parentheses to catch bothcode.py:9:9: SCR112 [error] Not enough arguments for format string: it needs 3, 2 givenOther rules in this group: SCR106 (bare except:, which also catches Java exceptions), SCR107 (mutable default arguments), SCR108 (duplicate dict keys), SCR110 (is with a literal), SCR113 (an exception caught after a broader one), SCR116 (raise "message"), SCR118 (a variable read where no assignment can have happened), SCR119 (return in finally) and, opt-in, SCR120 (closures in loops) and SCR122 (print in gateway scripts).
Ignition context
Section titled “Ignition context”| Rule | Reports |
|---|---|
SCR302 |
time.sleep or Thread.sleep in a Perspective event, transform or handler, which blocks the session's thread |
SCR303 |
a library module that calls system.* or does I/O when it is imported, in the importing script's scope |
SCR304 |
a library folder with its own code.py that hides its child modules |
SCR305 |
getSibling("X") or getChild("A/B") with a literal name that matches no component |
BIND009 |
a script transform that can finish without return (the property becomes null) |
# a button's onActionPerformed script, in a view whose root has a `Status` labelself.getSibling("Satus").props.text = "ok"view.json:42:46: SCR305 [error] No sibling component named `Satus` (in `root`)Unused code
Section titled “Unused code”SCR102 (unused imports), SCR103 (unused local variables), SCR104 (redefined before use), SCR105 (unreachable code), SCR109 (self-assignment), SCR114 (statements with no effect) and SCR123 (unused suppressions) keep scripts clean. SCR102 and SCR123 can fix their findings; whether the fix is safe depends on the rest of the workspace (see safe and unsafe fixes).
pylint, if you want it
Section titled “pylint, if you want it”SCR900 (opt-in) runs the real pylint over every script, each as its own module with the header Ignition compiles it with, and maps the messages back to exact locations. It drops messages that are wrong for Ignition (undefined system, library packages, Jython builtins). It is what ign-lint's PylintScriptRule maps to; for most projects the native SCR rules are enough and much faster.