Search code examples
pythonpackageexecutablepython-poetry

Use poetry to create binary distributable with pyinstaller on 'package'?


I think I'm missing something simple

I have a python poetry application:

name = "my-first-api"
version = "0.1.0"
description = ""
readme = "README.md"
packages = [{include = "application"}]

[tool.poetry.scripts]
start = "main:start"

[tool.poetry.dependencies]
python = ">=3.10,<3.12"
pip= "23.0.1"
setuptools="65.5.0"
fastapi="0.89.1"
uvicorn="0.20.0"

[tool.poetry.group.dev.dependencies]
pyinstaller = "^5.10.1"
pytest = "^7.3.1"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

I can run this and build this using Poetry, however, I would like to be able to create the executable with a poetry script as well.

Now I build it like this:

poetry run pyinstaller main.py --collect-submodules application --onefile --name myapi

I would like something like

poetry package to automatically create this executable as well. How do I hook that up?

Btw. ths does not work :(

[tool.poetry.scripts]
start = "main:start"
builddist = "poetry run pyinstaller main.py --collect-submodules application --onefile --name myapi"

Solution

  • I have found a solution using the pyinstaller API.

    As you may know already, Poetry will only let you run 'scripts' if they are functions inside your package. Just like in your pyproject.toml, you map the start command to main:start, which is the start() function of your main.py module.

    Similarly, you can create a function in a module that triggers Pyinstaller and map that to a command that you can run as poetry run <commmand>.


    Assuming you have a module structure like this:

    my_package
    ├── my_package
    │   ├── __init__.py
    │   ├── pyinstaller.py
    │   └── main.py
    └── pyproject.toml
    

    1. Create a file pyinstaller.py to call the Pyinstaller API

    The file should be inside your package structure, as shown in the diagram above. This is adapted from the Pyinstaller docs

    import PyInstaller.__main__
    from pathlib import Path
    
    HERE = Path(__file__).parent.absolute()
    path_to_main = str(HERE / "main.py")
    
    def install():
        PyInstaller.__main__.run([
            path_to_main,
            '--onefile',
            '--windowed',
            # other pyinstaller options... 
        ])
    

    2. Map the build command in pyproject.toml

    In the pyproject.toml file, add this

    [tool.poetry.scripts]
    build = "my_package.pyinstaller:install"
    

    3. From the terminal, invoke the build command

    You must do so under the virtual environment that poetry creates:

    poetry run build
    

    🎉 Profit