Python falls short in a few areas. For instance, Python isn’t the fastest language around, but third-party libraries like NumPy let you work around that. Where Python is most deficient, though, is packaging. That is, Python lacks a consistent internal mechanism for generating a standalone binary from an application. Go and Rust do this. Why can’t Python?
It mostly comes down to Python not having a culture of such use cases until relatively recently in its history. And so, only relatively recently did third-party modules start showing up that allow Python apps to be packaged as standalone binaries. PyInstaller — which I covered previously — is one such app. In this article we’ll look at an even more elegant and powerful utility for Python app packaging, BeeWare’s Briefcase.
However, there are two caveats worth pointing out about Briefcase. First, Briefcase doesn’t do cross-platform packaging; you need to build on the platform you’re deploying for. Second, Briefcase works best with apps that make use of a GUI toolkit of some kind. We’ll go into detail about these issues below.
What is BeeWare Briefcase?
Briefcase is part of a general suite of tools by BeeWare for creating apps, with the different pieces complementing each other. For instance, BeeWare’s Kivy lets you create cross-platform GUI apps in Python that run not only on all the major OS platforms but also on the web. But here we’ll focus on Briefcase, which can be used with or without the other tools.
Briefcase packages apps for all the OSes it supports by way of a common format for apps on that platform:
- Microsoft Windows (MSI installer)
- macOS (
- Linux (AppImage)
- iOS (Xcode project)
- Android (Gradle project)
To deploy on iOS or Android, you’ll need the development kits for those platforms.
One thing Briefcase does not support is cross-platform deployment. For instance, if you’re a Windows user, you can’t build a macOS app; you’ll need macOS to do that. Other app bundlers for Python are similarly limited, so this restriction is by no means exclusive to Briefcase.
Briefcase is also not a “compiler” — it doesn’t transform Python programs into their native machine-code equivalents. Your apps won’t run any faster when deployed as Briefcase apps than they do normally.
Briefcase project setup
Briefcase requires you to set up a dedicated project directory with its own virtual environment. If you’re not familiar with “venvs” yet, as Python virtual environments are called, it’s worth getting up to speed on them, as state-of-the-art Python development revolves heavily around them.
After you set up a venv and
pip install briefcase into it, you’ll use Briefcase’s own command-line tooling to set up, manage, and deliver Briefcase-packaged projects. This is akin to the way tools like Poetry work: Most of your high-level interactions with the project are through the tool, so you don’t have to manually create files or edit configurations.
To kick off a new Briefcase project, open the CLI in your project directory, activate the virtual environment (assuming you’re not using an IDE’s CLI to do that automatically), and type
briefcase new. This creates scaffolding in your project directory for a Briefcase project.
You’ll need to answer some questions about the project at first, and for most of them you can just press
Enter to accept the default. But one of the questions you’ll be asked — the last one, in fact — matters greatly: the choice of GUI framework to use.
One of BeeWare’s other offerings is a UI toolkit called Toga, for creating GUIs in Python programs using platform-native UI components. If you want to jump into learning Toga while also working with Briefcase, there’s nothing stopping you. Or you could select “None” and create a “headless” app that runs from the command line, or you could use a third-party UI toolkit or windowing system such as Pyglet or PyQT.
Note that if you install no UI toolkit, the app will have no console interactivity whatsoever — i.e., it won’t open a console window and it won’t print anything to the console. This is useful if you’re deploying a program that doesn’t require console interaction — for instance, if it runs as a local web server and uses a web browser for interaction. But there is as of yet no option to allow Briefcase programs with no UI package installed to run with a console.
Briefcase project structure
A freshly initiated Briefcase app directory comes with several files pre-installed:
- The top level of the app directory contains the project’s license,
pyproject.tomlfile, a sample README file in ReStructured Text format, and a
.gitignorefile that comes pre-customized with common directories to omit from any Git repository created for the project.
srcdirectory contains the source code of your app, with two subdirectories: one that contains the app (it has the same name as your project directory) and one that contains the app’s metadata.
- The app directory contains a
resourcesdirectory, which is used to store resources like application icons.
Briefcase project commands
briefcase command is how you perform most of your interactions with a Briefcase project. We covered the
new command above, which is used to set up a Briefcase project in a given folder. But you’ll typically need to use many other commands during the lifecycle of a Briefcase app, and some of them can be a little counterintuitive.
Here are the most common Briefcase commands you’ll use:
dev: When you’re inside an app directory, this command runs that app in dev mode. Dev mode lets you run the application with its full complement of installed libraries, but without needing to be formally packaged for delivery. Most of the time, when developing your application, you’ll test-run it with dev mode. If any dependencies have changed since the last time you ran
dev, use the
-dflag to update them.
build: Builds a copy of the application in the form needed to package it for distribution. This differs from
devin that you can build for different platforms if the scaffolding is installed.
update: Updates an application build. This is the quick way to make sure the build of your application has the most recent code, rather than using
build, which regenerates many more files. Pass the
-dflag to update dependencies, and the
-rflag to update resources (that is, to copy resources from the dev version of your app to the build version).
run: Runs the built version of the app. This essentially simulates running the packaged and deployed version of the application. Pass the
-uflag to update any code before running.
package: Creates an application installer package from the built version of the app. The end result of this is an artifact you can give to others to install your program — e.g., an .MSI on Windows.
Here are some of the less commonly used Briefcase commands:
create: Not to be confused with
createcreates the scaffolding for an application installer — a way to build the app’s installer for a particular platform. When you set up an app with
new, it comes with scaffolding for the platform you’re working on;
createlets you add scaffolding for another platform if needed.
upgrade: Upgrades the components used to package the app, such as the Wix framework.
publish: Publishes the packaged app to a publication channel such as an app store. (As of this writing, this feature doesn’t work yet.)
To sum up, this is the order in which you would use the Briefcase commands in the typical app lifecycle:
newto create the app
devto run the app as you work on it
buildto create a version of the app to be packaged for distribution
runto test-run the packaged version of the app
updateto keep the packaged version of the app up-to-date with code changes
packageto deploy the packaged version of the app with an installer
Briefcase app creation
Creating a Python program as a Briefcase app is much the same as creating any other Python app. The main issues involve the project structure. The app’s entry point is
__main__.py in the app directory, which loads
app.py from the same directory and executes
main(). When you initialize a project, it will be populated with placeholder versions of some project files, which you can build out or replace as needed.
If you’re transforming an existing project to use Briefcase, make sure you structure it in such a way that its entry point is what Briefcase expects. For instance, if you didn’t store the code in a
src directory, you’ll need to move your code into
src and fix any incompatibilities in its paths and directory structures.
The other thing to keep in mind is how to handle third-party dependencies. The
pyproject.toml file in your project directory controls which dependencies to add to the project. If your project is named
pyproject.toml will contain a section named
[tool.briefcase.app.myproject], with a
requires line that lists each requirement as they’d be specified in a
requirements.txt file. If your project needs, for instance,
black, you would set that line to
requires = ["regex","black"]. You’d then use
briefcase dev -d to update the dependencies for the development version of the project, and
briefcase update -d to update dependencies in the packaged version.
Briefcase app packaging and delivery
Once you run
briefcase package, you will see a redistributable for your program appear in a subdirectory of the project directory that corresponds to the platform you built for. For Microsoft Windows, for instance, the directory will be
windows, and the redistributable will be an
.msi file with the same name as your project. For Android and iOS, the results will be projects for Gradle and Xcode, respectively, and these will need to be compiled using those tools to be deployable to those platforms.