Summary
Roughly 45 public attributes across about 12 non-aggregator packages are absent from their package's __all__, so they are invisible to from <package> import * — and invisible to the export-integrity assertions added in #527.
Surfaced during the review of #527 and deliberately not folded into it: that PR fixes __all__ entries that name nothing (a hard AttributeError on star-import), whereas this is the inverse — real attributes that no __all__ names. Different defect, different blast radius, and folding it in would have turned a targeted fix into a wide API change inside an unrelated review.
Why the existing tests do not catch it
#527 adds a contract test over __all__, and its assertions are all of the form "every name in __all__ resolves". That is one direction only. An attribute that exists but is unlisted satisfies every one of them — the sweep has nothing to compare against, because __all__ is the input rather than the expectation.
The test's own docstring discloses this scoping, so the gap is documented rather than hidden. This issue is the follow-up it implies.
Why it is not obviously a bug
Omission from __all__ can be deliberate — it is the ordinary way to mark something as not-part-of-the-public-API while leaving it importable by path. So the 45 are a mix of genuine oversights and intentional exclusions, and the work here is triage rather than a mechanical fix:
- decide, per package, whether each unlisted public attribute is meant to be public
- add the genuinely public ones to
__all__
- for the intentional exclusions, consider whether a leading underscore would express the intent better than silence, since silence is indistinguishable from an oversight
The count of 45 is the reviewer's measurement and has not been independently re-derived. Re-measure before acting on it, and note the figure will move as #527 lands.
Suggested direction
A second contract assertion in the other direction — every public attribute of a package appears in its __all__, with an explicit allowlist for deliberate exclusions. That makes the intent reviewable instead of implicit, and it fails loudly when someone adds a public name and forgets the export. The allowlist is the load-bearing part: without it the assertion is unsatisfiable, and with it every exclusion becomes a decision somebody wrote down.
Notes
Aggregator packages are excluded from the count — they re-export by design and #527 already asserts their completeness separately.
Summary
Roughly 45 public attributes across about 12 non-aggregator packages are absent from their package's
__all__, so they are invisible tofrom <package> import *— and invisible to the export-integrity assertions added in #527.Surfaced during the review of #527 and deliberately not folded into it: that PR fixes
__all__entries that name nothing (a hardAttributeErroron star-import), whereas this is the inverse — real attributes that no__all__names. Different defect, different blast radius, and folding it in would have turned a targeted fix into a wide API change inside an unrelated review.Why the existing tests do not catch it
#527 adds a contract test over
__all__, and its assertions are all of the form "every name in__all__resolves". That is one direction only. An attribute that exists but is unlisted satisfies every one of them — the sweep has nothing to compare against, because__all__is the input rather than the expectation.The test's own docstring discloses this scoping, so the gap is documented rather than hidden. This issue is the follow-up it implies.
Why it is not obviously a bug
Omission from
__all__can be deliberate — it is the ordinary way to mark something as not-part-of-the-public-API while leaving it importable by path. So the 45 are a mix of genuine oversights and intentional exclusions, and the work here is triage rather than a mechanical fix:__all__The count of 45 is the reviewer's measurement and has not been independently re-derived. Re-measure before acting on it, and note the figure will move as #527 lands.
Suggested direction
A second contract assertion in the other direction — every public attribute of a package appears in its
__all__, with an explicit allowlist for deliberate exclusions. That makes the intent reviewable instead of implicit, and it fails loudly when someone adds a public name and forgets the export. The allowlist is the load-bearing part: without it the assertion is unsatisfiable, and with it every exclusion becomes a decision somebody wrote down.Notes
Aggregator packages are excluded from the count — they re-export by design and #527 already asserts their completeness separately.