bee41315e53da6fb5671f96302233c655906dfd5
max
  Wed Sep 9 06:56:49 2026 -0700
UniProt otto: rebuild the venv rather than relying on a system lxml

Correction to the previous commit: hgwdev has no system-wide lxml at all. The
import I tested was resolving to my own ~/.local/lib/python3.9/site-packages,
which cron never sees, because it runs the pipeline as otto.

So the environment is a virtualenv again, but a reproducible one. makeVenv.sh
deletes venv/ and rebuilds it from /usr/bin/python3, installs lxml, opens up the
permissions for otto, and then checks that lxml imports with an empty environment
so we know the venv stands on its own instead of borrowing from whoever ran it.
Built with --copies, so venv/bin/python is a real copy rather than a symlink that
would silently follow a system python upgrade while its compiled modules stayed
behind.

doUpdate.sh activates venv/ again and says to run makeVenv.sh if it is missing or
if the parser will not start. Verified: /usr/bin/python3 without per-user packages
cannot import lxml, the venv can, and after activation the parser runs and
converts real 2026_02 records.

Also shortened the README to how the pipeline is started and how it works, and
trimmed the history out of the code comments, leaving the ticket as the pointer.

refs #38300

diff --git src/hg/utils/otto/uniprot/doUpdate.sh src/hg/utils/otto/uniprot/doUpdate.sh
index 8b9f14220c8..67c7dbdaad7 100755
--- src/hg/utils/otto/uniprot/doUpdate.sh
+++ src/hg/utils/otto/uniprot/doUpdate.sh
@@ -1,69 +1,71 @@
 #!/bin/sh
 # configuration setup and cron wrapper for the doUniprot script
 
 cd /hive/data/outside/otto/uniprot || exit 1
 umask 002
 
 #echo WARNING: NOT DOWNLOADING
 #./doUniprot run --skipDownload
 
-# There is deliberately no virtualenv here anymore. uniprotToTab needs the lxml XML
-# parser, which on hgwdev comes from the system package python3-lxml and is upgraded
-# together with /usr/bin/python3. A virtualenv used to sit in venv/ instead, but its
-# python was only a symlink to /usr/bin/python3: when the system python moved from 3.6
-# to 3.9 the compiled lxml in the venv stopped loading, every monthly run died at the
-# parse step, and the tracks stayed on release 2024_06 for 19 months (redmine #38300).
-
 runLog=runLog.txt
 
 logRun() {
     echo "`date '+%Y-%m-%d %H:%M:%S'` $*" >> $runLog
 }
 
+# activate the python environment that has the lxml XML parser. Rebuild it with
+# ./makeVenv.sh if this fails.
+if [ ! -f venv/bin/activate ] ; then
+    logRun "PREFLIGHT-FAIL no venv"
+    echo "UniProt update did not start: venv/bin/activate is missing."
+    echo "Rebuild it with: cd /hive/data/outside/otto/uniprot && ./makeVenv.sh"
+    exit 1
+fi
+. venv/bin/activate
+
 # Do not spend 35 minutes downloading UniProt only to find out that the parser cannot
 # start. Run it with --help, which imports lxml and then exits, and stop here if that
 # fails. Invoked exactly the way doUniprot invokes it, so this tests the same python.
 if ! ./uniprotToTab --help > /dev/null 2>&1; then
     logRun "PREFLIGHT-FAIL uniprotToTab cannot start"
     echo "UniProt update did not start: ./uniprotToTab cannot be run."
     echo
-    echo "Almost certainly the lxml python module is missing. Check with:"
-    echo "    python3 -c 'import lxml.etree'"
-    echo "and see /hive/data/outside/otto/uniprot/README.txt for how to repair it."
+    echo "The lxml python module does not import. Rebuild the environment with:"
+    echo "    cd /hive/data/outside/otto/uniprot && ./makeVenv.sh"
     echo
     ./uniprotToTab --help 2>&1 | tail -20
     exit 1
 fi
 
 logRun "START"
 ./doUniprot run > lastRun.log 2>&1
 exitCode=$?
 logRun "END exit=$exitCode"
 
 if [ $exitCode -ne 0 ] ; then
     # lastRun.log is overwritten by the next run, so keep a copy. Without one, a
     # failure that nobody reads leaves no trace on disk at all.
     cp -f lastRun.log lastFail.log
     logRun "FAIL exit=$exitCode log=lastFail.log"
     echo "Big UniProt update FAILED, exit code $exitCode"
     echo
     echo "Full log: /hive/data/outside/otto/uniprot/lastFail.log"
     echo "Restart manually with:"
     echo "    cd /hive/data/outside/otto/uniprot && ./doUniprot run"
     echo "usually with the -p option to skip download and parsing of the gigantic XML."
     echo
     echo "Last 25 lines of the log:"
     tail -25 lastFail.log
     exit $exitCode
 fi
 
 if grep -q "are not newer than file in" lastRun.log ; then
     # UniProt had no new release this month. This is the normal case for most months,
     # so stay silent: otto crons only mail when something changed or something broke.
     logRun "NOCHANGE no new UniProt release on the server"
     exit 0
 fi
 
 logRun "OK updated to `cat tab/version.txt`"
 echo "Big UniProt update OK"
 echo "Now serving: `cat tab/version.txt`"