unguarded-entrypoint-call (SMP106)

SMP106🔧 Fix sometimes availableNamingAll rules

Flags a trailing call in a runnable module when importing that module would execute the call.

Why is this bad?

Python executes top-level statements when it imports a module. A command can run during import if its final statement calls its entrypoint without an if __name__ == "__main__": guard. A console script imports the configured callable before calling it, so an unguarded call can run the command twice.

Example

The unsafe fix wraps the call:

Example: fix

command.py before

1#!/usr/bin/env python
2
3
4def launch() -> None:
5    print("ready")
6
7
8launch()

command.py after

1#!/usr/bin/env python
2
3
4def launch() -> None:
5    print("ready")
6
7
8if __name__ == "__main__":
9    launch()

What it does

The rule checks the final top-level statement of a module. A direct call, a call inside sys.exit(...), and a call inside raise SystemExit(...) count. The callable’s name does not matter.

It reports a call when one of these signals identifies a runnable module:

  • The file is named __main__.py.

  • The file begins with a shebang or has an executable bit on Unix.

  • [project.scripts] or [project.gui-scripts] names the called object. In this case an unrelated trailing call, such as populate_cache(), is left alone.

An ordinary module with no such signal is left alone even if its final call is named main() or run(). Assignments such as CACHE = populate_cache() are not entrypoint calls. Calls under a main guard are allowed.

Python’s __main__.py guidance commonly uses a bare call there. This rule requires the guard because a package’s __main__.py can also be imported as a module.

Fix

The unsafe fix wraps a single-line statement in a main guard. It preserves an inline comment and the file’s line endings. Moving the statement changes import-time behavior, so the fix requires --fix --unsafe-fixes. Multiline statements and statements sharing a line with other code remain reports without a fix.