Skip to content

SCR102: unused-import

Imported name is never used

Code SCR102
Name unused-import
Category Scripts (SCR)
Default severity warning
Enabled by default yes
Fix available sometimes
Fix safety safe or unsafe, depending on the finding (see below)
ign-lint equivalent none

Reports imports whose name is never read. Usage is flow-sensitive: an import that is rebound before any read is unused even if the name is read later.

Reads are resolved the way Jython compiles them: a class body reads a name it binds from the class namespace and, before the class binds it, from the module (x = m.pi before m = 1 in the class reads the module's m); private names in class bodies are mangled (__m in class D reads _D__m); and the names the interpreter reads by itself count: __metaclass__ for a class without bases, AssertionError for assert, __name__ for class and function definitions.

Not reported:

  • from __future__ import ...;
  • names listed in the module's __all__ (and every module-level import when __all__ is built dynamically);
  • imports inside a try whose handlers catch ImportError (availability checks such as try: import json except ImportError: json = None);
  • scopes that read their names dynamically (locals(), vars(), eval, exec), every scope of a module with code that reads or writes a frame's or a module's names by itself (sys._getframe(), .f_locals, .tb_frame, e = eval, import __builtin__; the table is in docs/internal/fix-safety.md, "Frame routes: findings and fixes"), and module-level imports when any code of the module may read globals by name (globals(), eval, execfile, exec, sys.modules, frames, __dict__, func_globals). Reflection or introspection that is only a first hop (a Java member that leads to reflection such as .type, getattr with a computed name, sys.exc_info(), line numbers) does not silence the rule; it makes the fix unsafe;
  • imports in a class body: they are attributes of the class (self.os, C.os), used in ways iglint cannot follow (pyflakes and pylint do not report them either);
  • from x import y at module level of a library module: other scripts may use it as library.module.y (re-export). Plain import x is still reported there.

Unused imports slow down module loading (library modules are imported in every scope that uses them) and hide which dependencies a script really has. They also often point at a typo where the import was meant to be used.

import json
from java.lang import Thread
def onMessageReceived(self, payload):
self.view.custom.data = system.util.jsonDecode(payload)

Use instead:

def onMessageReceived(self, payload):
self.view.custom.data = system.util.jsonDecode(payload)

Removes the unused names from the import statement, or the statement when none of its names is used (replaced by pass when it is the only statement of its block; when every statement of a block is removed, the last one becomes pass). The safe fix empties the statement's lines and keeps them, so no line number changes; the unsafe fix removes the lines.

Safe only when all of these hold:

  • the import is known to succeed and to do nothing outside the importing module, so that removing it cannot change what runs: a Java class or package of the vetted JDK packages that was measured to exist in the Ignition image, or a plain import x of a standard library module that is known to have no import-time effects in Jython 2.7 (os, re, json, datetime, math, time, ...) and was measured to import, or from x import y of such a module when it is not a package and y was measured to exist; and no library package of the project (or of a project inheriting from it) has the same top-level name;
  • the module has no reflection or introspection route of any kind (iglint_python_semantic::dynamic: the routes that silence the rule, and the first hops that do not, such as .type or getattr(o, name)): code reached through one may read the import by name. The reason names the route;
  • the module uses only vetted code: every module, Java class, builtin and module attribute it uses is on iglint's allowlist, and every library module it imports or calls is vetted too (see docs/internal/fix-safety.md). Anything else may read the module's names in ways iglint does not know (cPickle.loads can call globals() in the caller's frame);
  • the module calls no library name that is not a plain function, class or literal constant (it may be eval or locals under another name);
  • the import is not the only binding of a name that is still read in its scope or a scope nested in it (print len(x) before import math as len in a function raises UnboundLocalError; without the import it calls the builtin);
  • the import does not come before a from __future__ import;
  • when the whole statement goes from a library module, nothing may read __package__: there every import that runs binds it (to None, in Ignition 8.3), so removing the only one turns a read into a NameError. A read or binding of __package__ or __warningregistry__ in the module, a binding of __name__, code that reads the module's globals by name, or another resource that reads them, reads the module's __dict__ or uses the module as a value makes the fix unsafe. (Event scripts run without __name__: their imports bind nothing.)

Unsafe otherwise: importing a project library module runs its top level, other Java classes run static initializers, some standard modules start threads (socket) or configure logging (logging), and a dotted import makes the submodule an attribute of its package for every other script. Also unsafe for top-level imports of library modules when iglint cannot see every use of the module's names (the module is used as a value, references are computed at run time, a resource of the project uses code that is not vetted, the project is inheritable or has scripts iglint does not model, or the file is linted outside its project), and for an import that may fail (a Java class or module attribute not measured to exist): removing it turns a failing import into a successful one.

Like every fix, it is unsafe while code in the workspace may read the files a fix edits (any file access, read, write, rename or delete, whose path is not a literal outside the workspace, or code that is not vetted); the project setting [fix] assume-scripts-do-not-read-workspace-files = true declares that no script does (see docs/internal/fix-safety.md, "File reads").

Top-level names of a library module are attributes of the module: an import that another resource uses as pkg.mod.name is not reported (also through another library module's import: m0.util.name when m0 does from pkg import util, at any depth; a module that another module's name may hold, try: from pkg import util / except: util = None, counts as used as a value, and so does every library module that a module used as a value imports), nor one that from pkg.mod import * in another resource binds there (every name not starting with _, or the names of __all__; any name when __all__ is changed in a way not read, such as __all__[:] = [...], __all__[1] = 'json', __all__.insert(...), __all__ += [...], an alias of it or a change from a function, and when its one binding is not an assignment to the bare name: from x import __all__, for __all__ in ..., with c as __all__, __all__, y = ...), nor one that another resource deletes (del pkg.mod.name: without the binding, a KeyError on 8.3.3) or changes with an augmented assignment (pkg.mod.name += 1 reads it first), also through aliases and other modules' imports. An assignment from another resource (pkg.mod.name = 5, also as a for, except or tuple target) or from a function of the module through global replaces what the import binds: the import is reported, but its fix is unsafe. A resource that reaches the module object other than by its names, with a special attribute (pkg.mod.__dict__, pkg.mod.__findattr_ex__(n), pkg.mod.__getattribute__('name') counts as pkg.mod.name) or a member of Ignition's module object (pkg.mod.dict, pkg.mod.getDict()), uses the module as a value: none of its imports is reported (see docs/internal/fix-safety.md, "Library module objects"). While a script of the project does not parse, what it will use is not known: the fix of a top-level import of a library module is unsafe.

Corresponds to W0611 unused-import. Differences: iglint also reports an import that is rebound before any read (pylint counts any read of the name), and does not report availability checks in try/except ImportError or from x import y re-exports in library modules (pylint does).


Generated from the rule metadata by iglint generate-docs; iglint rule SCR102 prints the same page in the terminal. Source: docs/rules/SCR102.md.