SLD2xx - Abstract Base Classes¶
SLD201: Prohibits importing ABC from the abc module.
SLD202: Prohibits using @abstractmethod decorator.
SLD203: Prohibits typing.cast. Casts bypass runtime safety; if you
genuinely need one, silence with # noqa: SLD203 to make the choice
visible at the call site.
Use typing.Protocol for interfaces instead:
# Bad
from abc import ABC, abstractmethod
class HTTPClient(ABC):
@abstractmethod
def get(self, url: str) -> Response:
...
# Good
from typing import Protocol
class HTTPClient(Protocol):
def get(self, url: str) -> Response:
...
SLD205: Flags too many distinct names imported from a single module
via from Y import .... Heavy reliance on one dependency is a
refactoring liability: changes to that module ripple through every
importer, and the wide surface area is hard to test, replace, or
summarize at the boundary.
The limit is 7 distinct names per module, aggregated across every
from Y import ... statement in the module. A name imported in two
statements counts once. The diagnostic is reported on the first such
statement.
Exemptions:
typingandastare allowlisted: both are broad-API stdlib namespaces where reaching for many members is structural rather than coupling.Imports inside
if TYPE_CHECKING:blocks are ignored entirely (theelse:branch of such anifstill runs at runtime and is checked).from . import ...(relative import with no module name) is skipped — the package boundary is already drawn by the dot.
To fix a real violation, either split the dependency across more focused call sites (so no single file carries the whole surface) or wrap the wide API behind a narrower local abstraction — a small class, function, or module facade that exposes only the operations this code actually uses.
# Bad (8 names from one module)
from somelib import a, b, c, d, e, f, g, h
# Bad (split statements still aggregate per module)
from somelib import a, b, c, d
from somelib import e, f, g, h
# Good (allowlisted)
from typing import Any, Iterable, Iterator, List, Mapping, Optional, Protocol
# Good (typing-only block ignored)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from somelib import A, B, C, D, E, F, G, H
# Good (relative import — package boundary)
from . import a, b, c, d, e, f, g, h
# Good (wrap the wide API behind a narrower facade)
# somelib_facade.py
from somelib import a, b, c # only what callers actually need
def do_thing(x):
return b(a(x)) + c()
# callers/...
from .somelib_facade import do_thing
SLD206: Prohibits any reference to NotImplementedError outside the
body of a function directly decorated with functools.singledispatch.
NotImplementedError is the conventional marker for an abstract method
that subclasses must override; stolid rejects subclassing for behavior
(see SLD201/SLD202/SLD401), so the only legitimate use is the default body
of a singledispatch generic function, where it signals that no
registered overload matched the argument type. Registered overloads
(@f.register) are not exempt — they implement the work and should
either handle the case or not be registered.
# Bad
class Handler:
def handle(self, event):
raise NotImplementedError
def parse(source):
raise NotImplementedError("subclass this")
# Good
import functools
@functools.singledispatch
def serialize(obj) -> bytes:
raise NotImplementedError(f"no serializer for {type(obj)}")
@serialize.register
def _(obj: User) -> bytes:
return json.dumps({"name": obj.name}).encode()
SLD207: Flags too many distinct attribute accesses on a single
module bound by import Y (or import Y as A). Like SLD205, this
is a coupling smell: the importer touches a wide swath of one
module’s public surface, so any change there ripples through every
caller.
The limit is 7 distinct attribute names per bound module.
Repeated accesses to the same attribute count once. The diagnostic
is reported on the import statement.
SLD205 and SLD207 are independent budgets: mixing from somelib
import ... with import somelib; somelib.x does not compound.
Each pattern must exceed 7 on its own to fire.
Exemptions:
typingandastare allowlisted: both are broad-API stdlib namespaces where reaching for many members is structural rather than coupling.Attribute accesses inside
if TYPE_CHECKING:blocks are ignored entirely (theelse:branch of such anifstill runs at runtime and is checked).
To fix, either split the dependency across more focused call sites or wrap the wide API behind a narrower local abstraction.
# Bad (8 distinct attribute accesses on one module)
import somelib
use(somelib.a, somelib.b, somelib.c, somelib.d,
somelib.e, somelib.f, somelib.g, somelib.h)
# Bad (alias does not help)
import somelib as sl
use(sl.a, sl.b, sl.c, sl.d, sl.e, sl.f, sl.g, sl.h)
# Good (allowlisted)
import typing
x: typing.Any = ...
# Good (typing-only block ignored)
from typing import TYPE_CHECKING
import somelib
if TYPE_CHECKING:
use(somelib.A, somelib.B, somelib.C, somelib.D,
somelib.E, somelib.F, somelib.G, somelib.H)