We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Seams, Modules, Packaging and Tooling step 32 of 36
Auditing a library for import-time side effects
Write the AST fitness function that fails the build when a library takes a decision that belonged to its users.
def audit_library(source: str) -> list[tuple[str, int, str]]:
Parse source with ast and return (code, lineno, detail) for every
finding, sorted by line number and then by code.
A statement runs at import time unless it is lexically inside a def,
async def or lambda body. Note the two consequences that make this
more than a top-level scan:
-
A class body runs at import.
class Registry: pool = ThreadPoolExecutor()is an import-time pool. -
A default argument is evaluated at import, at the
defline.def worker(pool: Executor = ThreadPoolExecutor()) -> None:creates the pool when the module loads, not when the function is called. This is ruff’sB008and it is why the naive “skip anything inside a FunctionDef” rule is wrong.
The rules:
| Code | Fires on | Where |
|---|---|---|
L001 |
a call whose callee’s final attribute is set_start_method |
anywhere in the module |
L002 |
a call whose callee’s final attribute is ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor or Pool |
import time only |
L003 |
a call whose callee’s final attribute is basicConfig |
import time only |
L004 |
a call whose dotted callee is exactly signal.signal |
import time only |
L005 |
an assignment (plain, augmented or annotated) whose target subscripts os.environ; or a call whose dotted callee is exactly os.environ.update, os.environ.setdefault or os.putenv |
import time only |
detail is the dotted callee as written ("multiprocessing.set_start_method",
"ThreadPoolExecutor"), except for the subscript form of L005, where it is
"os.environ".
Why L001 fires anywhere. set_start_method is process-global and may be
called once. A library that calls it — at import, in a helper, on the first
use, defensively — takes a decision the application cannot take back, and no
parameter the application can pass will undo it. There is no correct place
for that call inside a library, so there is no scope in which the rule should
be silent.
Why the others are scoped to import time. Creating a pool inside a
function is exactly right; creating one when somebody types import yourlib
forks worker processes inside their test suite, their linter and their
documentation build. logging.basicConfig() in a function the application
chose to call is a service; at import it silently reconfigures the root
logger for the whole process.
Your submission must pass mypy --strict. An ast.NodeVisitor subclass is
the natural shape: every visit_* method must be annotated
(self, node: ast.X) -> None, and node.func is an ast.expr you must
narrow before reading .id or .attr.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.