Packages with hub layouts are specifically the easiest to maintain __all__ by hand today because linters can partially help:
from foo import Foo
from bar import Bar # linter error: unused import
__all__ = ["Foo"]
You can combine this with a test to catch mispellings:
def test__all__imports():
import package
for name in package.__all__:
getattr(package, name)
If you don’t care about __all__ at runtime and use a hub layout, then all you really need is:
from foo import Foo as Foo
from bar import Bar as Bar
Frankly, I don’t care about __all__ at runtime. I find it hard to explain and magical, with an incredibly specific purpose. I don’t even think star imports and the public API should be two separate things, but I digress. I only use __all__ because I don’t have something better.
I don’t want to see language level support for __all__ because it’s not respected by tooling (LSPs /autocomplete/etc) today and I doubt this PEP will change that (Has any LSP author chimed in?)
For my personal projects, this PEP won’t really help:
# mypackage/__init__.py
from typing import Final
from .foo import FooClient
from .bar import Bar
__version__: Final = "0.1.1"
def get(a, b, c):
"""convenience wrapper. Use FooClient.get directly for advanced use cases"""
return FooClient(a, b).get(c)
__all__: Final = ("FooBarClient", "Bar", "get", "__version__")
Even with this PEP, I can’t get rid of __all__ entirely, and because I can’t get rid of __all__ entirely, all the issues remain. Not to mention, I’ll probably lose the tuple.
Lastly, this will also force any future proposals with stricter or different semantics to pick a new keyword, because changing export to, say, raise an error would be a backwards-incompatible change.