Skip to content

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-app and ~/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 .env or .toml file. 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.