Skip to content

Instantly share code, notes, and snippets.

@niittymaa
Forked from ForgottenUmbrella/publish_python.md
Created February 11, 2023 07:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niittymaa/d80b35b07b7e6ebf675d11cd1258974e to your computer and use it in GitHub Desktop.
Save niittymaa/d80b35b07b7e6ebf675d11cd1258974e to your computer and use it in GitHub Desktop.
How to publish Python apps for human beings

How to publish Python apps for human beings

So, you've created a Python app (be it a graphical user interface with Qt or the like, or a simple command line interface). Great! But how are others going to use it? Python applications often have dependencies (e.g. from third-party modules), and they also need a Python interpreter to run them. For a developer, installing all the necessary bits and bobs to make things work is okay, but that's unacceptable for a normal user - they just want to download the thing and run it.

Below are simple instructions to publish your app on the three main operating systems: Windows, macOS and Linux.

Table of contents

Windows and macOS

Windows users expect to download installers from random websites.

Users on macOS can deal with downloading a random .app file and dragging it into their Applications directory, because publishing on the App Store is a nuisance.

To remove the requirement of installing Python on the user's computer, we'll just bundle the entire interpreter with the app. Use some sort of freezing utility to do this; in this tutorial, cx_Freeze will be used. Note that frozen apps can only be run on the platform they were created on: if you freeze your app on Windows, it'll only run on Windows; same applies for macOS.

  1. Open a terminal.
  2. Install cx_Freeze with pip install cx_freeze.
    You might need to use sudo or --user if you're on macOS.
  3. Generate a setup.py file with cxfreeze-quickstart, and edit it to your liking (for example, to add an icon).
  4. If you're on Windows, create the installer for your app with python ./setup.py bdist_msi.
    If you're on macOS, create the .app with python ./setup.py bdist_mac.
  5. Host your file somewhere, like GitHub.

Check out https://cx-freeze.readthedocs.io/en/latest/faq.html if you have any problems.

Linux

On Linux, you can just package the .py script itself (preferably) for distro-specific formats, or on all distros with distro-agnostic tools like pip itself (since Linux distros often already have pip and Python installed), AppImages, snaps or Flatpaks. However, in the case of Flatpaks and snaps, your users will need to install the packaging system (which defeats the whole purpose of packaging with dependencies...)

AUR

The Arch Wiki is your friend.

  1. Create a setup.py.
  2. Create a PKGBUILD.
    See https://wiki.archlinux.org/index.php/Python_package_guidelines for Python specifics.
  3. Create an AUR account.
  4. Submit your package.

Users will install manually or with an AUR helper through the terminal.

RPM

  1. Create a setup.py.
  2. Create an RPM package with ./setup.py bdist_rpm.
  3. Extract the RPM with rpm2cpio dist/RPMFILENAMEHERE.rpm | cpio -idmv and edit the specfile inside SPECS if necessary (e.g. to include external, non-Python dependencies).
    Note that you'll need to install rpm2cpio and cpio if they're not already installed. Afterwards, rebuild with rpmbuild -ba SPECS/SPECNAMEHERE.spec.
  4. Host it somewhere, or use COPR.

DEB

This and this may be good tutorials.

Pip

This method assumes your user has (the right version of) Python installed. The guide is based on https://packaging.python.org/tutorials/distributing-packages/#packaging-your-project.

  1. Create a setup.py.
  2. Create a "source distribution" with ./setup.py sdist.
  3. Install wheel to create more efficient packages with sudo pip install wheel.
  4. Generate the more efficient package, known as a "wheel", with ./setup.py bdist_wheel.
    If your code runs fine on both Python 2 and 3 without any C extensions, include the --universal argument.
  5. Create a PyPI account to upload your package.
  6. Install twine to upload your package with sudo pip install twine.
  7. Finally, upload it with twine upload dist/*.

To install the package, users can run pip install --user with your package's name.

AppImage

Here are instructions and commands on how to make one for Python apps, duplicated for simplicity below.

  1. Install virtualenv.
  2. Download some helper functions with wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh.
  3. Source the helper functions with . ./functions.sh.
  4. Set variables for later: APP=APPNAMEHERE; LOWERAPP=${APP,,}, replacing APPNAMEHERE with your Python script's name.
  5. Create the package directory with mkdir -p $APP/$APP.AppDir/usr/bin/.
  6. Enter the directory with cd $APP/$APP.AppDir.
  7. Create a virtualenv for the dependencies with virtualenv usr.
  8. Install your app's third-party module dependencies using pip install followed by the module names.
  9. Save your Python script in usr/bin.
  10. Grant it execution permissions with chmod a+x usr/bin/$LOWERAPP.
  11. Prepare to finalise with get_apprun.
  12. Maybe create a desktop entry.
  13. Install with get_desktopintegration $LOWERAPP.
  14. Add an icon image to $LOWERAPP.png.
  15. Bundle dependencies with copy_deps; copy_deps; copy_deps.
  16. Finalise finalisation with delete_blacklisted; move_lib.
  17. Set the version number with VERSION= followed by your version number.
  18. Test that it works with ./AppRun.
  19. Get out of the dir to prepare to create an AppImage with cd ...
  20. Finally, generate the AppImage with generate_appimage.
  21. Host the file somewhere for users to download and click-to-run.

Snappy

For Python snaps, see this example and this tutorial.

  1. Install snapd using your distro's package manager.
  2. Create a setup.py that states the third-party module dependencies.
  3. Create a new directory for the snap with mkdir SOME_DIR.
  4. Initialise it as a snap with snapcraft init, and edit the resulting snap/snapcraft.yaml file.
  5. You may also want to create a .desktop file (if your app has a GUI) in a meta/gui directory, so users can launch it from their desktop environment.
  6. Create an Ubuntu account to publish the snap.
  7. Log in with snapcraft login.
  8. Reserve the package name with snapcraft register PACKAGE_NAME.
  9. Upload the snap with snapcraft push --release=[edge or stable] PACKAGE_NAME_*.snap.

Users will install it with snap install PACKAGE_NAME, and run it with COMMAND_NAME.

Flatpak

Here is the Flatpak documentation.

  1. Install flatpak and flatpak-builder with your package manager.
  2. Install a runtime and SDK for the flatpak. For this example, we'll be using the org.gnome.Sdk SDK and org.gnome.Platform runtime.
  3. Initialise a flatpak with flatpak build-init DIRECTORY DBUS_APP_NAME org.gnome.Sdk org.gnome.Platform.
  4. Create a setup.py.
  5. Install the script with flatpak build DIRECTORY ./setup.py install.
  6. Grant permissions with flatpak build-finish DIRECTORY PERMISSIONS --command=COMMAND_NAME.
  7. Generate a GPG key for hosting.
  8. Create a base64-encoded key with base64 --wrap=0 < PACKAGE_NAME.gpg.
  9. Generate a .flatpakref.
  10. Create a repo to host the package with flatpak build-export --gpg-sign=KEYID --gpg-homedir=PATH LOCAL_REPO DIRECTORY; flatpak --user remote-add --if-not-exists REMOTE_REPO LOCAL_REPO.

Users can install it by downloading the .flatpakref or running flatpak insall --from PACKAGE_NAME.flatpakref.
Unfortunately, with Flatpaks, using Python is cumbersome: flatpak run --command=python DBUS_APP_NAME --ARGS.

Conclusion

Hopefully you now know how to package a Python script or graphical program for Windows, macOS and Linux. For Windows, users expect installers; for macOS, .app executables are dragged into a directory or downloaded from the App Store; and for Linux, some sort of command line with the source code is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment