Welcome to the first article of a series about deploying a universally portable Python application.
What is a "Universally Portable" app?
A portable, or standalone, application is one that has no install-time or run-time dependencies other than the operating system.1 It is common to see this kind of application distributed as a compressed archive, such as a .zip or .tar.gz, or as an image, like .bin or .dmg.
A universal application is one that can run on all operating systems and architectures. Here, we use "universal" loosely to mean the three personal computer operating systems that make up over 90% of global market share: Windows (72.13%), MacOS (15.46%), and Linux (4.03%).2
Windows and Linux builds will target the amd64 (x86-64) architecture, and MacOS will build universal binaries that work on amd64 and Arm ("Apple Silicon" M-series Macs). Arm, aarch64 or arm32, builds for Linux would be possible locally but are not available in a GitHub Workflow, yet.
The Series
- This article: build the app locally with poetry
- Use a GitHub Release Action to automate distribution to PyPI, the Python Package Index so that Python users can install your app with pip and pipx.
- Add the universal portable application build to the GitHub Release Action using PyInstaller
- Add a Windows MSI installer build to the GitHub Release Action using WiX v4
- Add Linux .deb and .rpm installer builds to the GitHub Release Action using fpm
- Deploy to the Microsoft Store and
winget
- Deploy to the Mac App Store
- Deploy to the Debian Archive
The App
This article will focus on the application itself and the tooling to support it.
The app is a command line interface (CLI) that uses the built in argparse
module and takes one of three actions:
- no argument: print "Hello, World!"
-
-i
or--input
: print "Hello, World!", then "Press any key to exit...". This is used to create a double-clickable version of the application for Windows users -
-v
or--version
argument: print package version and exit
Take a look at the source code.
The Repo
The repository, python-distribution-example, can be cloned to your Windows, Linux, or MacOS environment.
The following are excerpts and explanations of the files that are relevant to running the app locally.
./jpsapp/
This is the Python module itself and contains all of the source code for the application.
-
__main__.py
: support running as a module -python -m jpsapp
-
main.py
: the app described above
./envr-default
This file defines the shell environment for common shells like bash, zsh, and PowerShell on Windows, MacOS, and Linux. The environment is activated by calling . ./envr.ps1
[PROJECT_OPTIONS]
PROJECT_NAME=jpsapp
PYTHON_VENV=.venv
./poetry.toml
Tells poetry to put the venv at .venv
. Note that this is where envr-default
has defined the venv location.
[virtualenvs]
in-project = true
./pyproject.toml
PEP 621 introduced the pyproject.toml
standard for declaring common metadata, replacing the need for requirements.txt
and most other configuration files. Poetry automates and simplifies the pyproject.toml
a bit further.
[tool.poetry]
name = "jpsapp"
version = "1.0.0"
description = "An example of Python application distribution."
authors = [
"JP Hutchins <jphutchins@gmail.com>"
]
readme = "README.md"
license = "Apache-2.0"
packages = [
{ include = "jpsapp" },
]
[tool.poetry.scripts]
jpsapp= "jpsapp.main:app"
[tool.poetry.dependencies]
python = ">=3.10, <3.13"
Most of this is standard, but here are a few entries that are unique to this app.
packages = [{ include = "jpsapp" }]
declares that jpsapp
is the only module we are packaging. This allows more Python modules to be added to the root of the repository.
jpsapp = "jpsapp.main:app"
declares that the command jpsapp
will execute the app
function from jpsapp.main
. Poetry documentation.
Finally, we are restricting the environment to use relatively up-to-date Python versions: 3.10, 3.11, and 3.12.
Dependencies
Python
If you have Python >=3.10 go ahead and use that. If not, install the most recent Python release for your system. There are many ways to do so, but I'll briefly offer my opinion:
- Windows: use the Microsoft Store or
winget
and take advantage of "App Execution Aliases". Whatever you do, make sure that bothpython
andpython3
call the Python you want, none of thispy
nonsense! - Linux: use your package manager, and maybe deadsnakes if you're on Ubuntu since they don't keep their Python packages current.
pipx
pipx
provides a much needed improvement to pip
when installing Python applications and libraries for use, rather than development.
Poetry
Because you installed pipx
, the pipx
method is probably the easiest!
Build the App
Now that you have cloned the repository and installed the dependencies, you can build and run the application.
-
poetry install
: on this first run it will create the venv at.venv
-
. ./envr.ps1
: activate the development environment
And that's it! jpsapp
should print "Hello, World!". Keep in mind that you can get the same execution with python -m jpsapp
, python -m jpsapp.main
, or python jpsapp/main.py
, etc.
To build the Python package distributions, simply run poetry build
. The Python .whl
and .tar.gz
packages will be built at dist/
, e.g. dist/jpsapp-1.0.0.tar.gz
.
In the next article, we will use a GitHub Workflow to release the package distribution to the PyPI so that other users can install your app with pipx
the same way that you installed Poetry!
Footnotes
- ^ "Portable application". Wikipedia.com. Retrieved 2024-03-11.
- ^ "OS Market Share". GS.Statcounter.com. Retrieved 2024-03-11.
Change History
- 2024-04-14: change
myapp
->yourapp
- 2024-04-18: change
yourapp
->jpsapp
Top comments (0)