This guide walks through turning template/ into a working PyShowwwcase
integration for a new backend pipeline. For the big picture see
README.md; for worked examples of the decisions involved, see
implementations.md.
Throughout, replace <impl> with your integration's short name (e.g. pipex),
lowercase, matching [a-z0-9_].
- The backend pipeline already runs as a Docker image driven by
docker compose run -d <pipeline>, reading its inputs from a known work directory and writing aLAST_RUN_IDfile when it finishes. (Get this working in isolation first — PyShowwwcase only orchestrates it.) - A Linux host with Docker + docker compose, cron, and nginx (with TLS if you're serving over HTTPS).
Decide three things up front:
- The args contract — what file does the pipeline read its inputs from, and
in what format? (a batch CSV, a JSON config, a command line…). This becomes
ARGS_FILEand the payload yourrun.htmlbuilds. - The completion signal — the pipeline must write
LAST_RUN_ID(containing the run id it read fromRUN_ID_FILE) when done. This is how the queue knows a job finished. - The show strategy — self-hosted page, redirect to a viewer, or generate-a-manifest-then-redirect (the three patterns in implementations.md).
cp -r template implementations/<impl>You now edit four areas: settings, the run form + start_run, the show page +
show_project, and the setup/ wiring.
In the # FROM HERE YOUR CUSTOM PYSHOWWWCASE SETTINGS section, set the values for
your integration. Keep the same set of keys as the template (only change
values) — every project shares the same settings surface:
| Setting | What to set |
|---|---|
REDIRECT_BASE_URL |
/pyshowwwcase/<impl> |
STATIC_URL |
/pyshowwwcase/<impl>/pyshowwwcase/static/ |
BASE_DIRECTORY |
/opt/pyshowwwcase/files/pyshowwwcase/<impl>/ |
RUN_DIRECTORY |
./work/pyshowwwcase/<impl>/ (in-container path the launcher uses) |
ARGS_FILE |
the filename your pipeline reads its inputs from |
RUN_ID_FILE |
usually run_id.txt |
OUTPUT_FILE |
base name of the download zip (usually results) |
SHOW_DIRECTORY |
subfolder where show writes display assets (show, or output if the pipeline writes there directly) |
SHOW_IMAGE_SIZE, ALLOW_GUESTS, DEMO_USERLIST, GUEST_*, USER_* |
per-deployment policy |
CSRF_TRUSTED_ORIGINS / ALLOWED_HOSTS are derived from
PYSHOWWWCASE_<IMPL>_SERVER_NAME in .env — don't hardcode them.
database.py reads the MySQL credentials from environment variables named
MYSQL_PYSHOWWWCASE_<IMPL>_USER / _PASSWORD / _DATABASE. Make sure the
<IMPL> token matches the keys you'll define in .env (this is a classic
copy-paste bug — the template historically still pointed at PIPEX).
This is where you turn user input into the pipeline's args file.
run.html: build the form fields your pipeline needs. ThestartRun()JS assembles the payload string (the future contents ofARGS_FILE) and puts it in the hiddenpayloadfield, then submits. Use placeholders for values the server must fill in — by convention###dir###for the project's output directory and###img###for an uploaded-image path. Sanitize any field that becomes a filename on disk.start_run(in the# CUSTOM CODEblock ofviews.py): validate the project, clear stale results (SHOW_DIRECTORY), substitute the placeholders againstRUN_DIRECTORY+ the project's relative path, enforce the run-count limit, and insert thequeuerow. Keep a cheap guard that the payload actually contains your expected placeholder/token before enqueuing.
See proticelli for building the payload as JSON via json.dumps (safe against
injection from user fields) and hpaseg for the uploaded-file-vs-external-URL
substitution.
Pick one of the three strategies and adapt the matching reference:
- Self-hosted page (pipex): in
show_project, convert each pipeline output into a display image underSHOW_DIRECTORY, build a context of the URLs, andrendershow.html.serve_imagestreams files fromSHOW_DIRECTORY(always throughsafe_path). - Redirect to a viewer (proticelli): record the project in the session and
redirectto the external viewer URL; add aviewer(request, filename)view + aviewer/<path:filename>route inurls.pyto serve the files it needs. - Generate a manifest, then redirect (hpaseg): convert outputs to display
layers, write the viewer's manifest (e.g. an EllViewer
metadata.csv) intoSHOW_DIRECTORY, then redirect to the viewer pointed at that manifest; serve both via theviewerview.
If show opens an external viewer, set home.html's showProject() to
window.open(...) so it launches in a new tab (see proticelli/hpaseg).
Do not edit anything outside the custom block, the two templates, the
settings, and setup/. The base views, queue.py, security.py, forms.py,
the shared HTML, and urls.py (apart from any viewer route you add) must stay
byte-identical to template/. Fixes to shared behavior belong in template/ and
are then propagated to all implementations.
This mirrors how the reference implementations are deployed. Assume the install
root is /opt.
Install the backend pipeline (e.g. at /opt/<pipeline>) as a Docker image and
confirm docker compose run -d <pipeline> works against its work directory,
reading ARGS_FILE and writing LAST_RUN_ID. Set its resource limits in its own
docker-compose.yml. Don't proceed until the pipeline works standalone.
# the app itself (without its setup/ subfolder)
cp -r implementations/<impl> /opt/pyshowwwcase/<impl> # drop the setup/ subfolder here
# the deployment artifacts go to /opt
cp implementations/<impl>/setup/docker-compose.yml /opt/
cp implementations/<impl>/setup/.env.example /opt/.env
cp implementations/<impl>/setup/pyshowwwcase.sh /opt/pyshowwwcase/Edit /opt/.env: set real MariaDB passwords, the SECRET_KEY, the ports,
PYSHOWWWCASE_<IMPL>_FILES_ROOT (the host data root), and
PYSHOWWWCASE_<IMPL>_SERVER_NAME (your hostname(s)). Review
/opt/docker-compose.yml (network name, volume mounts, the *_FILES_ROOT mount).
cd /opt
docker compose up -d --build maria_db
docker exec -it maria_db bashInside the container:
-
create the database
pyshowwwcase_<impl>and the userpyshowwwcase_<impl>, granting it all privileges on that database; -
load the schema from
setup/init.sqlinto the database (createsusersandqueue); -
add a PyShowwwcase user. Passwords are unsalted SHA-256. For example, for the password
sysadm:insert into users (user_id, username, password, settings) values (1, 'sysadm', 'd577adc54e95f42f15de2e7c134669888b7d6fb74df97bd62cb4f5b73c281db4', '');
Generate a hash with:
python -c "from hashlib import sha256; print(sha256('sysadm'.encode()).hexdigest())". Users are added directly in the DB like this (there's no signup flow).
- Add the two lines from
setup/crontabto root's crontab. They run every minute:sh /opt/pyshowwwcase/pyshowwwcase.sh— the host launcher (flock-guarded);docker exec -i pyshowwwcase_<impl> sh run_queue.sh— the in-container queue state machine.
- Add
setup/pyshowwwcase_nginxto nginx (e.g. intosites-availableand symlink intosites-enabled). It proxies/pyshowwwcase/<impl>to the web container and serves/pyshowwwcase/<impl>/pyshowwwcase/static/directly. Setserver_nameandclient_max_body_size(large, for uploads). Reload nginx.
cd /opt
docker compose up -d --build pyshowwwcase_<impl>Browse to https://<your-host>/pyshowwwcase/<impl> and log in.
Wiring check.
pyshowwwcase.shmoves the args file into the pipeline's work dir, so the launcher's paths must agree with the pipeline's work directory (e.g.HPASEG_WORK=/data/<impl>for hpaseg). If runs enqueue but never start, check: the pipeline's work-dir env var, that both cron jobs are firing, and thatLAST_RUN_IDis being written/read at the path the queue expects.
- Log in (or use guest access), create a project, upload a test input.
- Open run, fill the form, submit. The job should appear on the queue page as queued, then running.
- Within ~1–2 minutes the launcher starts the pipeline; when it finishes,
LAST_RUN_IDis written and the next queue tick clears the job. - Open show — your results should render (or the viewer should open).
- download the project to confirm the result zip is produced.
If something stalls, the logs to check are <FILES_ROOT>/queue_log.txt (the
state machine), the pipeline's own log.txt in its work dir, and the gunicorn
logs in the web container.
-
cp -r template implementations/<impl> - settings:
REDIRECT_BASE_URL,STATIC_URL,BASE_DIRECTORY,RUN_DIRECTORY,ARGS_FILE,SHOW_DIRECTORY(+ policy limits) -
database.pyenv token matches<IMPL> -
run.html+start_runbuild the args payload and substitute placeholders -
show.html+show_projectimplement the chosen show strategy (+viewerroute if redirecting to a viewer) - generic files left byte-identical to
template/ -
setup/.env.example(copied to/opt/.env),docker-compose.yml,init.sql,pyshowwwcase_nginx,pyshowwwcase.sh,crontabfilled in for the host - DB created + schema loaded + a user inserted
- both cron jobs installed; nginx configured & reloaded
- smoke test passes end to end