DEV Community

JP Hutchins
JP Hutchins

Posted on • Updated on

Building a Universally Portable Python App

Python Logo

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

  1. This article: build the app locally with poetry
  2. 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.
  3. Add the universal portable application build to the GitHub Release Action using PyInstaller
  4. Add a Windows MSI installer build to the GitHub Release Action using WiX v4
  5. Add Linux .deb and .rpm installer builds to the GitHub Release Action using fpm
  6. Deploy to the Microsoft Store and winget
  7. Deploy to the Mac App Store
  8. 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:

  1. no argument: print "Hello, World!"
  2. -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
  3. -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.

./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
Enter fullscreen mode Exit fullscreen mode

./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
Enter fullscreen mode Exit fullscreen mode

./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"
Enter fullscreen mode Exit fullscreen mode

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 both python and python3 call the Python you want, none of this py nonsense!
  • Linux: use your package manager, and maybe deadsnakes if you're on Ubuntu since they don't keep their Python packages current.

pipx

Installation instructions

pipx provides a much needed improvement to pip when installing Python applications and libraries for use, rather than development.

Poetry

Installation instructions

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

  1. ^ "Portable application". Wikipedia.com. Retrieved 2024-03-11.
  2. ^ "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)