It’s been a while since my last post. I have been developing the flashcard web app (vocaBull (2); vb2).

I successfully deployed vb2 in PythonAnywhere (PA) at https://achiwa912.pythonanywhere.com. Note that it’s still alpha quality with debug info shown. In this post, I write what I did for the deployment for the record.

Prep on local MacBook

Update required Python versions

I use python 3.14 for the development on mac. As PA supports up to 3.13, I needed to change the python version for the project.

  • Change required python version (was >=3.14) in vb2/pyproject.toml
requires-python = ">=3.13"

Build frontend

As setuptools invoked by uv don’t support .org for README, delete this line from vb2/pyproject.toml

readme = "README.org"  # delete this line.  Or, replace with .md/.rst/.txt
  • uv lock (and git commit)
uv lock
  • build. .js and .css are created under vb2/fe/dist
pnpm build
  • commit vb2/fe/dist
    • remove dist from .gitignore (I have .gitignore at /vb2 as well as /vb2/fe)

Serve frontend via Flask

For dev, I run pnpm dev for dev webserver. I needed to update api.py to serve static files I just built. I use an env variable SERVE_FRONTEND to distinguish if it’s running on mac or in PA.

  • Add at the end of api.py:
# ---------------------------------------------------------------------------
# Serve the built Vue frontend (production only)
# ---------------------------------------------------------------------------
import os as _os
from flask import send_from_directory

if _os.environ.get("SERVE_FRONTEND") == "1":
    FRONTEND_DIST = _os.path.join(
        _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))),
        "fe",
        "dist",
    )

    @app.route("/", defaults={"path": ""})
    @app.route("/<path:path>")
    def serve_frontend(path):
        full = _os.path.join(FRONTEND_DIST, path)
        if path and _os.path.exists(full):
            return send_from_directory(FRONTEND_DIST, path)
        return send_from_directory(FRONTEND_DIST, "index.html")

Google Cloud Console

Google accepts auth only from pre-configured URLs. To avoid being rejected by Google,

Deploy vb2 on PythonAnywhere (PA)

Update PA system image

I was using haggis system image which supports up to python 3.10. https://help.pythonanywhere.com/pages/ChangingSystemImage

  • Go to Account > System Image
  • Change current system image to innit
  • Change all default python version to use 3.13

Wipe all and start fresh

  • Open console and wipe everything
rm -rf ~/*
rm -rf ~/.*
rm -rf /tmp/*
  • Close console and start a new one
    • Should use python 3.13. Confirm: python -V

Create environment

  • Create virtual env and git clone vb2 from the github repo
cd ~/
mkvirtualenv vb2-venv
git clone https://github.com/achiwa912/vb2.git
  • Edit vb2/.python-version
3.13 # from 3.14
  • Install libraries
    • The UV_PROJECT_ENVIRONMENT var was the key — uv sync otherwise wants to create .venv
    • --python 3.13 option might not be needed
UV_PROJECT_ENVIRONMENT=/home/achiwa912/.virtualenvs/vb2-venv uv sync --no-install-project --python 3.13
  • Note:
    • uv pip install -e . didn’t work because setuptools can’t decide between be/ and fe/.
    • uv pip sync uv.lock didn’t work as it expects requirements.txt, not uv.lock.

Create a new webapp

  • delete achiwa912.pythonanywhere.com at Web tab
  • add a new web app > Manual configuration (note: NOT Flask)
    • Python 3.13
    • Source code: /home/achiwa912/vb2
    • Working directory: /home/achiwa912/vb2
    • Virtualenv: /home/achiwa912/.virtualenvs/vb2-venv
  • Open console and edit /var/www/achiwa912_pythonanywhere_com_wsgi.py
import sys
import os
from dotenv import load_dotenv

project_folder = os.path.expanduser('~/vb2')
load_dotenv(os.path.join(project_folder, '.env'))

path = '/home/achiwa912/vb2'
if path not in sys.path:
    sys.path.insert(0, path)

os.chdir(path)

from be.api import app as application
  • vim .env at ~/vb2
export SERVE_FRONTEND=1
export CORS_ORIGINS=http://localhost:5173,https://achiwa912.pythonanywhere.com
export JWT_SECRET_KEY=<snip>
export GGL_CLIENT_ID=<snip>
  • For bash console (optional?)
echo 'set -a; source ~/vb2/.env; set +a' >> ~/.virtualenvs/vb2-venv/bin/postactivate
  • From Web tab, reload achiwa912.pythonanywhere.com

Frontend auth fix

If I accessed https://achiwa912.pythonanywhere.com, I saw http error 422 on /books. Looking into it, the cause was that the Authorization header was set to null. To fix the issue, I added onRequest middleware in fe/src/api/client.ts and removed per-call headers.

After the fix, it works beautifully.