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

4

5

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"
Cornfield answered 1/5, 2023 at 8:58 Comment(0)
W
11

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

Windywindzer answered 26/9, 2023 at 16:12 Comment(1)
This will also include the build script in the tar.gz and the whl, this is only a problem if you also wanna distribute your program such that it is also installable with pipChoanocyte
B
6

I recently made the poetry-pyinstaller-plugin to cover this use-case, here is the link to the plugin.

Step 1. Install the plugin

$ poetry self add poetry-pyinstaller-plugin

It will include pyinstaller in your poetry environment.

Step 2. Add PyInstaller target(s) in pyproject.toml

Given original author requests, following block must be added:

[tool.poetry-pyinstaller-plugin.scripts]
my-api = { source = "application/main.py", type = "onefile", bundle = false }

Step 3. Build binaries

Running legacy poetry build command. Binaries are generated before sdist/wheel archive (PyInstaller binaries can be included in wheels with bundle feature)

Expected output in dist folder:

<pyproject_root_dir>
  └── dist/
      ├── pyinstaller/
      │   └── my-api
      ├── application_0.0.0.tar.gz
      └── application-0.0.0-py3-none-any.whl

Dependencies are included from current python environment, including venv support from Poetry.

Banister answered 23/2, 2024 at 23:5 Comment(0)
H
4

You are looking for a way to define simple commands to run specific tasks. This is not (yet) supported by Poetry directly. But there is at least one Poetry Plugin that support this: https://github.com/nat-n/poethepoet

Harden answered 7/5, 2023 at 15:58 Comment(1)
I have a non-exhaustive list of some more tools similar to this here: Python task runners. As far as I know, none of them are specific to Poetry, they can all work without Poetry, but some seem to have a tighter integration with Poetry available than others.Danged
S
-2

You can define a custom script in your pyproject.toml file that runs the pyinstaller command with the desired options.

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

You can also customize the name of the script (build in this example) to something more descriptive if you wish.

Note that this will only build the executable and not create a package. To create a package, you can use poetry build, which will create a source distribution and wheel package for your application.

Scurlock answered 1/5, 2023 at 9:8 Comment(6)
Thanks. I was hoping I could somehow hook into the package step, but this seems like a improvement as well.Cornfield
Also, can you add an example on how exactly this should go in the toml file? Just adding this line in [tool.poetry.scripts] section does not work.Cornfield
Sure! To define a custom script that builds your executable and creates a package, you can add the following lines to your pyproject.toml file: build = "poetry run pyinstaller main.py --collect-submodules application --onefile --name myapi" package = "poetry build" With this configuration, you can now run poetry run build to build your executable, and poetry run package to create both a source distribution and a wheel package for your application.Scurlock
this is not working. I put an example in the questionCornfield
@AkvaAlex Do you have some resources to link to about the solution you are presenting here? Documentation? -- As far as I can tell, either some critical info has been left out of this answer, or this answer is incorrect.Danged
@AkvaAlex where do we put the line exactly? You know you can't just add a random line in a toml fileChoanocyte

© 2022 - 2025 — McMap. All rights reserved.