tl;dr¶
You can now deploy BeeWare apps as web apps on
PythonAnywhere with a single command. Install the
pythonanywhere-briefcase-plugin,
run briefcase publish web static, done. There’s a
step-by-step tutorial if you want to
try it right now.
The more interesting story is how a feature request turned into an architectural discussion that shaped how Briefcase handles publication in general, and how the whole thing went from proposal to a Briefcase 0.4.0 release feature in three weeks.
A hackathon idea¶
This started at an Anaconda company meeting in Lisbon. PythonAnywhere is part of Anaconda, and Anaconda supports the BeeWare open source project by employing its maintainer, Russell Keith-Magee. BeeWare lets you write native GUI apps in Python for mobile, desktop, and the web, all from a single codebase. It tackles one of the “black swan” risks Russell identified in his PyCon 2019 keynote: Python’s lack of a clear deployment story on desktop and mobile. His “How to build a cross-platform GUI with Python” at EuroPython 2025 shows where things stand now. BeeWare’s packaging tool, Briefcase, handles building those apps for each target platform.
So for once we were all in the same room. Russell (who’d traveled the longest way to get there) was at the table when the idea came up ahead of hackathon: what if you could deploy a BeeWare web app to PythonAnywhere with a single command?
Briefcase could already build BeeWare apps for the web:
PyScript and Pyodide run Python in the browser via
WebAssembly, and Toga’s web backend
renders the GUI using Shoelace web components. That part worked. But
getting the result online still meant uploading files by hand. The
briefcase publish command existed in stub form, that did nothing.
By the end of the hack day we had a proof of concept working:
pythonanywhere-core calls wired directly
into patched Briefcase code, deploying a built app to PythonAnywhere. Rough, but
it proved the idea was sound.
Back home, that hackathon prototype got turned into
a proposal on the Briefcase GitHub:
a briefcase publish web command targeting PythonAnywhere. Check for an API
token, read the username from config, upload the built www directory,
create the web app, set up a static file mapping, reload. One command, app
online.
Why not just bolt it in?¶
The discussion could have stayed narrow: “add PythonAnywhere support to Briefcase, ship it, move on.” It didn’t.
Russell raised a good question: should publication channels be part of Briefcase itself, or third-party plugins? Briefcase already had a plugin pattern for platforms and debuggers, and baking in a specific hosting provider felt wrong. What about some other PaaS or some internal corporate server? None of those belong in core.
So the scope grew. Instead of one integration, the work became a plugin
system: a standalone package (like pythonanywhere-briefcase-plugin),
registered
via entry points,
discovered dynamically when a user runs
briefcase publish.
What the discussion sorted out¶
A few things got nailed down over the next few days on GitHub:
pythonanywhere-core shouldn’t become a hard requirement for all Briefcase
users. If you don’t publish to PythonAnywhere, you shouldn’t need to install
anything related to it.
The original proposal had a flat config key like pythonanywhere_username.
Russell suggested a nested table instead:
[tool.briefcase.app.myapp.web.pythonanywhere]
username = "myuser"
Multiple channels can coexist in the same config without stepping on each other.
Plugins register under a scoped entry point group like
briefcase.channels.web.static, so the registration itself encodes
platform and format. One installed channel auto-selects; multiple require
--channel.
briefcase publish shouldn’t fail because you forgot to run
briefcase build. The chain is publish -> package -> build, each step
running the previous one if needed.
Not every publish looks the same either. An artifact model (build -> package -> publish a zip/tarball) and a direct model (build -> publish files directly). PythonAnywhere fits the second; there’s no remote unzip API, so the plugin uploads files individually.
Core verifies and prepares the app. The plugin handles the actual upload. A
protocol
(PublishCommandAPI) defines the contract between them.
From discussion to code¶
Within a week there was enough consensus to start building.
The plugin interface PR
landed on Briefcase: a BasePublicationChannel ABC, dynamic channel
discovery, the publish -> package -> build cascade, and placeholder channels
for iOS and Android that raise clear errors. It also stripped out the old
hardcoded s3 stub.
The review was iterative. Russell shortened publication_channels to
channels in module paths (“we’re not likely to have some other concept of
channel”), kept the full BasePublicationChannel class name, and worked
through edge cases in live testing, including an Android packaging format
issue where distribution_path() needed to know the format before the
cascade could run. He pushed fixes directly and added a -u flag for
update-before-publish.
Merged about a week later.
In parallel, the hack day POC was being rewritten as a proper plugin:
pythonanywhere-briefcase-plugin.
It implements BasePublicationChannel, wires up pythonanywhere-core for
file uploads and webapp configuration, and registers itself under the
briefcase.channels.web.static entry point. The rough patched code from
Lisbon turned into a clean, tested package.
Docs PRs added the plugin to both the Briefcase docs and the BeeWare tutorial. Then Briefcase 0.4.0 shipped with publication channels as a headline feature. And then pythonanywhere-briefcase-plugin v0.0.1 was released: the first channel plugin for Briefcase, and the reason all of this started.