Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle relative imports in a package's __main__.py #2560

Open
JonathonReinhart opened this issue Apr 13, 2017 · 16 comments
Open

Handle relative imports in a package's __main__.py #2560

JonathonReinhart opened this issue Apr 13, 2017 · 16 comments
Labels
feature Feature request

Comments

@JonathonReinhart
Copy link

JonathonReinhart commented Apr 13, 2017

I have a runnable package with __main__.py:

foo/
    __init__.py
    __main__.py

I can execute this package by running python -m foo. However you cannot directly create a PyInstaller application from this package:

  • If you try pyinstaller -F foo, you'll get IOError: [Errno 21] Is a directory.
  • If you try pyinstaller -F foo/__main__.py it will build, but then you'll get ValueError: Attempted relative import in non-package when you try to run it, presumably because it took __main__.py out of the context of its package.

The work around is to create a small "stub" application that lives outside of the package and calls into it:

from foo import main
main()

...and then point PyInstaller at the stub.

It seems that PyInstaller should be able to create an application by being pointed at the package directory.

I'm sorry if this has already been addressed, but I've been unable to find anything in the documentation or anywhere else:

@ghost
Copy link

ghost commented Apr 13, 2017

See subzero.

@htgoebel
Copy link
Member

We'd appreciate a pull-request implementing both entry-points and support for __main__.py

@htgoebel htgoebel added the good first issue This is a good issue if you want to start working on PyInstaller label Apr 15, 2017
@djhoese
Copy link
Contributor

djhoese commented Apr 21, 2017

@JonathonReinhart What does the resulting .spec file look like after running with foo/__main__.py? I have a project that I've been building fine for years that points to a __main__.py module.

@JonathonReinhart
Copy link
Author

@davidh-ssec I suspect you're not using relative imports in __main__.py, and thus it's okay that __main__.py is being "removed" from the context of the package. It's as if you moved __main__.py outside of the package and ran it (as my workaround prescribes).

I put this example together, for reference: https://github.com/JonathonReinhart/pyinstaller-package-example

When I build it (using pyinstaller 3.2) this spec file is generated:

# -*- mode: python -*-

block_cipher = None


a = Analysis(['foo/__main__.py'],
             pathex=['/home/jreinhart/projects/pyinstall-package-example'],
             binaries=None,
             datas=None,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='foo_installed',
          debug=False,
          strip=False,
          upx=True,
          console=True )

And when I go to build + run it:

$ ./try_to_build_main_dot_py.sh 
16 INFO: PyInstaller: 3.2
16 INFO: Python: 2.7.13
16 INFO: Platform: Linux-4.10.6-200.fc25.x86_64-x86_64-with-fedora-25-Twenty_Five
17 INFO: wrote /home/jreinhart/projects/pyinstall-package-example/foo_installed.spec
21 INFO: UPX is available.
22 INFO: Extending PYTHONPATH with paths
['/home/jreinhart/projects/pyinstall-package-example',
 '/home/jreinhart/projects/pyinstall-package-example']
22 INFO: checking Analysis
27 INFO: checking PYZ
29 INFO: checking PKG
30 INFO: Bootloader /usr/lib/python2.7/site-packages/PyInstaller/bootloader/Linux-64bit/run
30 INFO: checking EXE

Traceback (most recent call last):
  File "foo/__main__.py", line 1, in <module>
    from . import main
ValueError: Attempted relative import in non-package
Failed to execute script __main__

@dhowland
Copy link

dhowland commented Apr 2, 2018

Did you ever solve this? I have the same problem.

@htgoebel htgoebel added the pull-request wanted Please submit a pull-request for this, maintainers will not actively work on this. label Apr 2, 2018
@djhoese
Copy link
Contributor

djhoese commented Apr 2, 2018

@dhowland The "workaround" as far as I'm concerned is to not use relative imports in your __main__.py module. I think I was doing non-relative imports in all of my packages anyway because I was under the impression that they weren't preferred so I do from mypkg.subpkg import x everywhere.

@bittner
Copy link
Contributor

bittner commented Oct 2, 2018

so I do from mypkg.subpkg import x everywhere

Note that's it's not necessary to use absolute imports everywhere. It's sufficient to use absolute imports in the script you're supplying to PyInstaller, only.

MapleCCC added a commit to MapleCCC/QuickFund that referenced this issue Jul 1, 2020
…script with relative imports inside. The workaround is to use an intermediate stub file pyinstaller_entry.py. Reference to pyinstaller/pyinstaller#2560
@HaujetZhao
Copy link

So far, this is the workaround:

foo/
    __init__.py
    __main__.py
    component.py
    launcher.py
# __main__.py
from .component import *
...
def main():
    ...
if __name == '__name__':
    main()
# launcher.py
# Just copy __main__.py code to here and replace .component into component
from component import *
...
def main():
    ...
if __name == '__name__':
    main()
pyinstaller -F foo/launcher.py

@ericoporto
Copy link

hey, it would be cool if I could pass a setup.py to pyinstaller and it would figure it all out. Specifically also picking up the data directories.

@bwoodsend
Copy link
Member

Freezing a __main__.py will work as long as there are no relative imports (from . import). Freezing setuptools entry-points directly is never going to happen because setuptools entry-points should generally also be exposed as python -m equivalents so that user's who don't add their scripts directory to PATH can still use them. And even if they're not, it should be a simple case of putting from your_project import main; main() in a Python script and PyInstaller-ing that. I'd rather not introduce a whole new syntax just to save two lines of user code.

@poul1x
Copy link

poul1x commented Feb 12, 2022

Using only absolute imports in __main.py__ and executing command pyinstaller --onefile "C:\some-path\__main__.py" worked for me.

Project structure:

─mypackage
│   __init__.py
│   __main__.py
│
├───app
│   │   app.py
│   │   database.py
│   │   receiver.py
│   │   util.py
│   │   __init__.py
│   │
│   ├───components
│   │   │   main_window.py
│   │   │   mq_properties.py
│   │   │   __init__.py
│   │   │

Contents of __main__.py:

from logging.config import dictConfig
import coloredlogs # Needed for pyinstaller
import yaml

from mypackage.app import runApp     # Absolute import

if __name__ == "__main__":

    # Configure logging
    with open("logging.yaml") as f:
        dictConfig(yaml.safe_load(f))

    # Run application
    runApp()

Function runApp:

import sys
from PyQt5.QtWidgets import QApplication
from .components.main_window import MyWindow

def runApp():

    app = QApplication(sys.argv)

    myWindow = MyWindow()
    myWindow.show()

    sys.exit(app.exec())

@bwoodsend bwoodsend added feature Feature request and removed good first issue This is a good issue if you want to start working on PyInstaller pull-request wanted Please submit a pull-request for this, maintainers will not actively work on this. labels Nov 4, 2022
@bwoodsend bwoodsend changed the title Pyinstall a package with __main__.py Handle relative imports in a package's __main__.py Nov 4, 2022
marph91 added a commit to marph91/joplin-sticky-notes that referenced this issue Apr 14, 2023
I. e. remove relative imports from __main__.py.
See: pyinstaller/pyinstaller#2560
@j5155
Copy link

j5155 commented May 7, 2024

Hi, just wanted to mention that I'm still encountering this bug in 2024.
Would appreciate a fix, or at least more warning/documentation about this.

j5155 added a commit to unofficial-rev-port/REVHubInterface that referenced this issue May 7, 2024
@bwoodsend
Copy link
Member

How would resolving the full module path work in this scenario? i.e. If the hypothetical entry point script foo/bar/__main__.py contained from . import pop, how is PyInstaller supposed to know if foo/bar/pop.py represents foo.bar.pop or bar.pop?

@LewisGaul
Copy link

How would resolving the full module path work in this scenario? i.e. If the hypothetical entry point script foo/bar/__main__.py contained from . import pop, how is PyInstaller supposed to know if foo/bar/pop.py represents foo.bar.pop or bar.pop?

I'd assume the most obvious solution to be supporting the equivalent of python3 -m foo.bar.pop rather than the entrypoint being specified as a path.

@bwoodsend
Copy link
Member

We'd probably want to deprecate the current pyinstaller -m shorthand option first. (Arguably --manifest is sufficiently obscure to not want a shorthand anyway.)

bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Aug 3, 2024
If we're ever to deal with the issue of relative imports in the entry
point script (pyinstaller#2560) then we'll probably need to support Python's -m
option and therefore remove the current interpretation of PyInstaller's
-m option.
bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Aug 3, 2024
If we're ever to deal with the issue of relative imports in the entry
point script (pyinstaller#2560) then we'll need to support Python's -m option and
therefore remove the current interpretation of PyInstaller's -m option.
bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Aug 3, 2024
If we're ever to deal with the issue of relative imports in the entry
point script (pyinstaller#2560) then we'll need to support Python's -m option and
therefore remove the current interpretation of PyInstaller's -m option.
bwoodsend added a commit that referenced this issue Aug 3, 2024
If we're ever to deal with the issue of relative imports in the entry
point script (#2560) then we'll need to support Python's -m option and
therefore remove the current interpretation of PyInstaller's -m option.
@hutcho
Copy link

hutcho commented Aug 7, 2024

I think the best fix is just including documentation about how you can't have relative imports in the .py file you give to pyinstaller, but you can (maybe?) keep relative imports in all the other .py files. This resolved the issue for me. I think relative imports may get more common in the future.

nebkat added a commit to nebkat/esp-idf-monitor that referenced this issue Jan 5, 2025
nebkat added a commit to nebkat/esp-idf-monitor that referenced this issue Jan 8, 2025
nebkat added a commit to nebkat/esp-idf-monitor that referenced this issue Jan 8, 2025
espressif-bot pushed a commit to espressif/esp-idf-monitor that referenced this issue Jan 10, 2025
fix: Use absolute import in __main__ for pyinstaller/pyinstaller#2560

Closes IDFGH-14380

See merge request espressif/esp-idf-monitor!79
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Feature request
Projects
None yet
Development

No branches or pull requests