is there a way to prevent or alert in case a member is imported from within a module?
from fixmycode@feddit.cl to python@programming.dev on 21 Nov 2023 17:46
https://feddit.cl/post/1048707

Let’s say I have the following structure:

my_module/
  __init__.py
  utilities.py

and __init__.py contains

from .utilities import SomeUtilityFunction

Is there a way to prevent or alert developers when they do

from my_module.utilities import SomeUtilityFunction

instead of

from my_module import SomeUtilityFunction

The problem arose when a few modules started using a function that was imported inside a module in which it wasn’t used, while also being available on the module’s __init__.py, so after linting the file and removing the unused import my tests started failing.

any other advice for situations like this?

#python

threaded - newest

sebsch@discuss.tchncs.de on 21 Nov 2023 18:05 next collapse

I am normally define the interface to models while defining __all__.

Defined in the __init__ it allows to define a whitelist what can be Imported from the outside.

docs.python.org/3/tutorial/modules.html#importing…

Chais@sh.itjust.works on 21 Nov 2023 18:27 collapse

That’s not correct. __all__ is not a whitelist. It is only the list used for

from module import *

If you have a module with submodules foo, bar and baz and __all__ = [“foo”, “bar”] it will not prevent you from importing baz manually. It just won’t do it automatically.

sebsch@discuss.tchncs.de on 21 Nov 2023 19:08 collapse

It works exactly like one. You get a warning if you try to import something not defined in it. The docs are just very confusing here ;)

<img alt="" src="https://discuss.tchncs.de/pictrs/image/d31fc58c-3182-456c-b39a-42d46f5055c0.png">

Chais@sh.itjust.works on 21 Nov 2023 19:41 collapse

Bullshit!

module/__init__.py:

__all__ = ["foo", "bar"]

module/foo.py:

def foo():
    print("foo")

module/bar.py:

def bar():
    print("bar")

module/baz.py:

def baz():
    print("baz")

main.py:

from module import *
from module import baz

if __name__ == "__main__":
    print("main")
    foo.foo()
    bar.bar()
    baz.baz()

Output:

$ python main.py 
main
foo
bar
baz

No errors, warnings or anything.

sebsch@discuss.tchncs.de on 21 Nov 2023 19:50 collapse

You’re running python without linters? Interesting approach.

Chais@sh.itjust.works on 21 Nov 2023 20:02 collapse

You can’t expect the user to have one.

Chais@sh.itjust.works on 21 Nov 2023 18:40 collapse

You could guard it.
__init__.py:

_GUARD_SOME_UTILITY_FUNCTION = True
from .utilities import SomeUtilityFunction

utilities.py:

def SomeUtilityFunction():
    if not _GUARD_SOME_UTILITY_FUNCTION:
        raise SomeException("Helpful error message")

Take this with a grain of salt, as I’m typing this on my phone and haven’t actually tried it.

Alternatively there’s the import-guard package on PyPI. No idea if it’s any good, though. Just something a quick search brought up.

Edit:
Ok, I tried my suggestion and it doesn’t work.

sebsch@discuss.tchncs.de on 21 Nov 2023 19:41 collapse

This approach seems quite overkomplex. Instead of having these errors on runtime, stuff like this should sit in linter rules of any kind.

Chais@sh.itjust.works on 21 Nov 2023 19:51 collapse

It’s only useful during development there.