Skip to content

bare-imports

A bare import os whose only use is os.environ, however many times it appears, names a single symbol the reader could pull in directly with from os import environ. A namespace reached through many distinct attributes earns its bare form, because the prefix then organizes a wide surface a from import would only scatter, and an aliased import (import numpy as np) is the author's deliberate namespace handle.

leans on those signals, flagging an unaliased bare import only when its namespace is reached through at most max-attributes distinct attributes (default 4) and never as the bare object itself, recommending the explicit from package import name rewrite and leaving the rewrite itself to a future migration pass that picks up the lint output.

The rule weighs each imported namespace by the distinct attributes read off it at module scope, attribute reads nested inside functions and class bodies still resolving to the module-level binding. A namespace used as the bare object (passed to a call, bound to another name) cannot collapse into a from import, so it passes whatever its attribute count, and a function-local import sits outside the module scope the rule measures. An entry on the allow list preserves the bare form, including its dotted submodules (numpy.linalg inherits the exemption from numpy), and allow-aliased (on by default) exempts every aliased import. When a downstream migration pass acts on the lint output, the rewrite hands off cleanly to the rest of the import surface:

sorts the resulting block, aligns the import keyword, and lands the gap between groups. The lint itself is non-rewriting, so the diagnostic surfaces without touching the source.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueToggle the rule on or off
allowlist of module names[]Modules whose bare-import form is preserved whatever their attribute count
allow-aliasedbooltrueExempt every aliased bare import (import x as y) from the rule
max-attributesinteger4Distinct-attribute count at or below which a bare import is flagged

The allow list holds bare package names, where any dotted submodule of an allowlisted package inherits the exemption. Set allow-aliased to false for a project that wants every import to name its symbols, aliased or not. Lower max-attributes to flag only the narrowest imports, or raise it to catch wider ones.

The Canonical Case

import os is reached only through os.environ, one distinct attribute, so a from os import environ rewrite would cover every use no matter how many times it appears. import torch fans out across five distinct attributes, past the max-attributes budget of 4, so its bare form is left untouched.

python
import os
import torch

os.environ["HOME"]
os.environ["PATH"]

torch.tensor(torch.zeros(3))
torch.nn.Linear(8, 8)
torch.optim.Adam
torch.cuda.is_available()

More Examples

Both numpy and torch are reached through a single attribute, so each would draw the lint. The allow list blesses numpy as a bare import the project keeps, leaving only torch flagged.

os is reached through one attribute and draws the lint, yet bare-imports emits no fix. The source text is left exactly as written, the diagnostic standing as a report rather than a rewrite.

max-attributes = 6 widens the lint to imports reached through up to six attributes. os here touches five, past the default budget of 4 but within the raised one, so it draws the lint where the default would spare it.

No Change

os is reached through one attribute and would draw the lint, but its import sits inside a # fmt: off block, so the diagnostic is suppressed at the import line.

No Change

os is passed as the module object to register, not reached through an attribute, so no from os import ... rewrite could replace it. A bare use of the namespace itself spares the import whatever else it touches.

No Change

os is reached through five distinct attributes, one past the max-attributes budget of 4, so the bare import stays. A from os import ... would have to name every symbol, at which point the namespace import earns its keep.

No Change

import os as o carries the author's deliberate namespace handle, so the allow-aliased default exempts it from the rule whatever its attribute count.

For per-line opt-outs, the Suppression chapter covers the # prose: ignore[bare-imports] directive.