2

I have a structure like the following:

package/
    setup.py
    ...
    package/
        __init__.py
        _foo.py
        subpackage/
            __init__.py
            bar.py

I am trying to import _foo from inside bar:

# bar.py
from .._foo import baz

def myfunc():
    baz()
    # stuff

while running bar.py as a script (for example, in a Jupyter Notebook, or even with python bar.py. If I run this as a module using python -m package.subpackage.bar it works, but I want to find ways around this). I can't get it to work:

>>> from . import _foo
ImportError: cannot import name '_foo' from '__main__' (unknown location)
# changing __name__ to 'package' doesn't work etiher
>>> from ._foo import baz
ModuleNotFoundError: No module named '__main__._foo'; '__main__' is not a package
>>> from .. import _foo
ValueError: attempted relative import beyond top-level package

>>> sys.path.append(os.getcwd())
>>> from .._foo import baz
ValueError: attempted relative import beyond top-level package
>>> from ._foo import baz
ModuleNotFoundError: No module named 'package' 

I intend this to be released for public use, so tricks that only work for my machine are not really useful for me (referring to some sys.path or PYTHONPATH tricks I found).

mariogarcc
  • 386
  • 4
  • 13
  • How was `bar` imported? – Davis Herring Dec 28 '18 at 20:51
  • @DavisHerring I am doing this inside `bar` because it requires from methods/variables `_foo` has. I'm not using this whole thing in an actual scenario with the package being built and placed in `site-packages` or whatever, is that the problem? And if so, it means I can't actually test the code inside the package as I'm writing it in `bar`? – mariogarcc Dec 28 '18 at 20:55
  • Possible duplicate of [Relative imports for the billionth time](https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time) – Davis Herring Dec 28 '18 at 21:24
  • You can certainly test your code—but you must test a package *as a package* and a module *as a module*, not as a script. This doesn’t mean you have to install it: a Python package organized in the obvious way can be run directly, by putting the directory **above it** on `sys.path` (perhaps via `PYTHONPATH`). – Davis Herring Dec 28 '18 at 21:27
  • @DavisHerring can you post an answer explaining that, please? I read the duplicate and it works if I run it as specified, via `python -m ...`, but it'd be much faster if I could do something, at least for while I'm working inside the kernel, to do the import from inside the script. – mariogarcc Dec 28 '18 at 21:52
  • Define “from inside the script”—preferably by editing the question, which would perhaps make it clearly not a duplicate. – Davis Herring Dec 28 '18 at 22:40
  • @DavisHerring done, hopefully. If there is no solution and you're sure about it (it very well can be), please post it as an answer instead of as a comment so I can accept it. – mariogarcc Dec 28 '18 at 23:03
  • 1
    Running scripts from within a package is just not supported in Python. Give up on that. Instead you want to look at [entry-points](https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points). – wim Dec 28 '18 at 23:12
  • @wim Thank you wim, will take a look. But please post it as the answer -- it is the answer! – mariogarcc Dec 28 '18 at 23:22
  • 1
    I don't think it's necessary because there are already other good answers about this on site. [Explain Python entry points?](https://stackoverflow.com/q/774824/674039) – wim Dec 28 '18 at 23:28
  • @wim I meant that you could post your previous comment as an answer, instead of as a comment. "Running scripts from within a package is not supported in Python. Instead you want to take a look at entry-points" -> this is a good answer for my question; no need to explain entry-points here. :-) – mariogarcc Dec 28 '18 at 23:37
  • OK, I'll add the answer. – wim Dec 28 '18 at 23:50

1 Answers1

3

Running scripts from within a package is not supported in Python, because Guido considered that an antipattern.

Existing solutions are running bar as a module:

python -m package.subpackage.bar

Or creating a console_scripts entrypoint:

# in setup.py
from setuptools import setup

setup(
    ...
    entry_points={
        "console_scripts": [
            "mybarscript=package.subpackage.bar:myfunc",
        ]
    }
)

When package is installed, a Python script called mybarscript will be autogenerated. It will hook into the callable myfunc defined in bar.py.

wim
  • 338,267
  • 99
  • 616
  • 750
  • Does the `myfunction` you mention here refer to the `myfunc` defined in `bar.py` (I did it in an edit as there was no `myfunction` defined previously) or does it have to do with something else related to entry-points? If the latter, don't worry. If the former, tell me to change the function name to "myfunction" or consider changing it in your answer to "myfunc" so it is more clear for guests. – mariogarcc Dec 29 '18 at 00:29
  • Yes it should have been `myfunc` (edited). This callable must not accept positional arguments, and it should parse the command line args and setup logging if necessary. Make sure `bar.py` doesn't execute anything at import time. – wim Dec 29 '18 at 00:30
  • Very well. I will here add the two links you mentioned about the [documentation for entry-points](https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points) and the [SO question for the same topic](https://stackoverflow.com/questions/774824/explain-python-entry-points). Feel free to add them inside the answer, if you want. (I will delete the comment afterwards, if you do.) – mariogarcc Dec 29 '18 at 00:34
  • Sure. For future reference, you may simply edit answers directly. – wim Dec 29 '18 at 00:47