We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Seams, Modules, Packaging and Tooling step 6 of 36
What does this module actually export?
Write the check a type checker runs: given a module’s source, decide what its public surface is.
Verdict = tuple[Literal["all", "inferred", "dynamic"], frozenset[str]]
def public_surface(source: str) -> Verdict:
Parse source with ast and look only at module-level statements —
names bound inside a function or a class body are not part of the module
namespace.
If a module-level assignment to __all__ exists, it decides everything:
-
the value is a list or tuple whose every element is a string literal →
return
("all", frozenset(those strings)). Exactly those, even if a name is not defined anywhere in the file.__all__is the author’s declaration and it wins. -
the value is anything else — a call, a name, a concatenation, a
comprehension, a list containing a non-string — → return
("dynamic", frozenset()). A non-literal__all__is not a surface a static tool can verify, and reporting that honestly is the whole job.
Otherwise infer the surface under the typing spec’s re-export rules and
return ("inferred", names):
| Statement | Contributes |
|---|---|
import os |
nothing — an import is not a re-export |
import os.path |
nothing |
import os as os |
"os" — the redundant alias is the spec’s opt-in |
from .core import Thing |
nothing |
from .core import Thing as Thing |
"Thing" |
def f / async def f / class C |
the name, unless it starts with _ |
NAME = ... or NAME: T = ... |
the name, unless it starts with _ |
NAME: T with no value |
nothing — it binds nothing at runtime |
An import X as Y counts only when Y == X exactly. import os.path as path
is a rename, not a re-export.
The production consequence. This is the rule that decides whether deleting
a line from your __init__.py is a refactor or a breaking change. An
unaliased from .core import Thing reads as “I needed this to write the
module”; Thing as Thing reads as “this is API”. Ship the first and users
will import it anyway; delete it later and you break them in a patch release.
Your submission must pass mypy --strict, which is most of the exercise
here: every ast node arrives typed as ast.stmt or ast.expr and you must
narrow with isinstance before touching .value, .targets, .elts,
.name or .id. ast.Constant.value is Any, so narrow that too before
putting it in a set[str].
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.