Python Server Setup
Every web tool the classes use (the support queue, the 3D print queue, and
whatever comes next) is served from one PythonAnywhere web app at
tools.npa.school. That one app runs Python 3.13 with a single shared
virtualenv and hands each request to one of several independent Flask apps
based on the URL prefix, using Werkzeug's DispatcherMiddleware. Each app is its
own codebase with its own config file and its own MySQL database; the apps share
nothing but the Python process and the virtualenv.
This page is the record of how the server is put together and the conventions every app follows. The step-by-step for mounting a new app is on Adding a Web App.
What Is Mounted Now
| Mount | App | Code directory | Config | Database |
|---|---|---|---|---|
/ |
Landing page (static one-pager, no links) | defined inline in the WSGI file | none | none |
/queue |
Support queue (virtual hand-raising) | ~/supportqueue-app |
.env in the app directory |
misterwillis$supportqueue |
/3dp |
3D print queue | ~/printqueue-app |
printqueue.toml in the app directory |
misterwillis$printqueue |
The site runs on a paid PythonAnywhere account, which is what allows the custom
domain. The account username is misterwillis, which is why it appears in every
database name and in the MySQL host name below.
Key Locations
| What | Where | Notes |
|---|---|---|
| WSGI file | /var/www/tools_npa_school_wsgi.py |
Editable from the Web tab. One commented section per app; each section adds its mount to a MOUNTS dict, and a single DispatcherMiddleware call at the bottom wires it all up. No secrets ever go in this file. It holds only paths and imports. |
| Virtualenv | ~/.virtualenvs/tools-venv |
Set in the Web tab's Virtualenv field. It must contain the union of every app's dependencies. Currently: Flask, Authlib, requests, python-dotenv, waitress, markdown, nh3, and PyMySQL. |
| Databases | Databases tab | MySQL. One database per app, named misterwillis$<appname>. Host: misterwillis.mysql.pythonanywhere-services.com. |
| Error log | Linked from the Web tab | Always the first stop when the site 500s after a change. The traceback from the last failed start is at the bottom of the file. |
Conventions
These are the rules every app on the server follows. None of them is enforced by PythonAnywhere; they are enforced by the person adding the next app.
- Directory naming. Each app lives in the home directory as
~/<name>-app(for example~/printqueue-appand~/supportqueue-app). The Python package inside may be named differently; only the directory follows the convention. - Config and secrets live inside the app directory, in a
.envor.tomlfile. They never go in the WSGI file and never in the repo or zip that deploys the code. Config files are created once on the server and survive code updates. - One MySQL database per app, named
misterwillis$<appname>. Use MySQL, not SQLite. PythonAnywhere's network filesystem makes SQLite unsafe under concurrent web workers. - Mount paths are short (
/queue,/3dp) and never change once an app has users, because the Google OAuth redirect URI embeds the mount path. - Updating an app is a four-step routine: zip the project locally, upload the zip via the Files tab, unzip it over the app directory in a console, and Reload on the Web tab. Config and data live outside the zip, so an update never touches them.
Google Sign-In
All apps reuse the same Google OAuth client, an External client in the Google Cloud console. Each app does its own filtering of which Google accounts it accepts; the client itself is shared.
Because the client is shared, the client ID and client secret are the same for
every app, and adding an app needs no IT re-approval. The approval attaches
to the client, not to its list of redirect URIs. What a new app does need is its
callback URL added to the client's Authorized redirect URIs, in the form
https://tools.npa.school/<mount>/auth/callback. That procedure, and the table
of secrets every app's config file needs, are on
Adding a Web App.
Rollback and Troubleshooting
- Site-wide 500 after a change. Open the Web tab error log and read the bottom of the file. The WSGI file is the usual suspect: all apps fail together because one section failed at import time.
- Take a misbehaving new app offline without touching the others by deleting (or commenting out) its WSGI section and reloading. Its directory, config, and database are untouched, and it can be re-mounted later.
- Virtualenv problems. The Web tab's Virtualenv field can be blanked to fall back to system Python (only system-wide packages will be available). This is useful for bisecting whether the venv is the problem.
- MySQL connections. Connections are per-request, and PythonAnywhere
enforces
max_user_connections. Apps should open one connection per request and close it on teardown. Both current apps do.
Platform Limits Worth Remembering
- The request body cap is about 100 MB. Keep any app's upload limit at or below that.
- Web workers are recycled, and the site is never put to sleep, but
Scheduled Tasks (the Tasks tab) are the mechanism for cron-style jobs,
such as the planned nightly pruning of stored print files. They run in the
home directory with system Python by default, so call the venv's interpreter
explicitly:
~/.virtualenvs/tools-venv/bin/python <script>. - Consoles, scheduled tasks, and the web app all see the same home directory, so a script can safely share an app's config file.
- MySQL databases count toward the account's storage quota.