3cb6f55ce985430c052a0c9cbbbec43e711665b7 max Thu Sep 17 06:38:09 2026 -0700 hgGeneGraph: python 3.8 compatibility and repairs to the feedback form, refs #38369 #Preview2 week - bugs introduced now will need a build patch to fix cgi.escape() was removed from python in 3.8, so all three calls raised AttributeError rather than escaping anything. Use html.escape(). The 'remove interaction' form could not work at all: - the INSERT listed its values positionally against a six column table, which put the timestamp into the comment column and the comment text into the time column. - it never committed, so nothing survived the end of the request. - the CREATE TABLE went through sqlQuery(), which reads cursor.description and therefore cannot run a statement that returns no rows. Now that sqlTableExists() works, the CREATE is skipped where the table is already there, instead of failing with 'table already exists'. diff --git src/hg/hgGeneGraph/hgGeneGraph src/hg/hgGeneGraph/hgGeneGraph index 2be268a93bd..7ee374f979d 100755 --- src/hg/hgGeneGraph/hgGeneGraph +++ src/hg/hgGeneGraph/hgGeneGraph @@ -19,49 +19,49 @@ # dark blue, dashed = only low-throughput data # dark blue, thickness = low-throughput data + text # dark blue + dashed = only pathway data # code review # - os.system is not a security risk here, no variables go into the cmd line # - mysql statements are not escaped, instead all CGI vars are checked for non-alpha letters # hgFixed tables required for this script: ggLink (main table with gene-gene links), # ggLinkEvent (details about link), ggEventDb (details about links from databases), # ggEventText (details about links from text mining), ggDoc (details about documents for ggEventText) # ggGeneName (symbols), ggGeneClass (HPRD/Panther class) # these are default python modules on python 2.7, no errors expected here -import sys, cgi, os, string, urllib.request, urllib.parse, urllib.error, operator, hashlib +import sys, cgi, os, string, urllib.request, urllib.parse, urllib.error, operator, hashlib, html from sys import exit from collections import defaultdict, namedtuple from os.path import * #These two lines should be commented out whenever committing this file into git. #They activate stack traces to stdout which can in certain cases reveal the mysql password, #when there is a Mysql connection problem. #import cgitb #cgitb.enable() # import the UCSC-specific library sys.path.append(join(dirname(__file__), "pyLib")) try: from hgLib3 import cgiArgs, cgiSetup, cgiString, printContentType, printMenuBar, \ sqlConnect, sqlQuery, errAbort, cfgOption, runCmd, cgiGetAll, printHgcHeader, \ printHgcSection, getNonce, getCspMetaHeader, jsOnEventById, \ jsInlineFinish, webStartGbNoBanner, htmlPageEnd, hConnectCentral, \ - sqlTableExists, readSmallFile, forceUnicode + sqlTableExists, sqlUpdate, readSmallFile, forceUnicode except: print("Content-type: text/html\n") print("Cannot find the directory cgi-bin/pyLib in Apache. This is an installation error.") print("All all parts of cgi-bin installed? Did you do 'make' in kent/src/hg/pyLib?") import hgLib3 hgLib3.forceUnicode = True import pymysql.cursors # not using feedback button for now. Fan would have liked it, but not sure how we can write # to any form of database. # the list of allowed chars in cgi args: digits, letters and dashes legalChars = set(string.digits) @@ -510,31 +510,31 @@ return filtLinks def buildGraph(conn, gene, geneCount, minSupp, addNeighbors): """ get (gene,gene) links from database and annotate with weights and tags only get links with minSupp articles for each link """ if geneCount <= 1: errAbort("Sorry, you have to show at least two genes.") try: links = queryLinks(conn, gene=gene) except pymysql.Error: errAbort("Cannot find the gg* tables in hgFixed. This mirror's database may need updating.") if len(links)==0: - errAbort("Sorry, the gene %s is not a valid gene symbol or is not present in any gene interaction database." % cgi.escape(gene)) + errAbort("Sorry, the gene %s is not a valid gene symbol or is not present in any gene interaction database." % html.escape(gene)) links = filterLinks(links) lowLinks = defaultdict(set) graphLinks, lowLinks = splitHighLowLinks(links, gene, minSupp, lowLinks, geneCount) if addNeighbors: # create the links between all other genes, be less stringent about these otherGenes = set() for genes, pmids in graphLinks.items(): otherGenes.update(genes) if gene in otherGenes: otherGenes.remove(gene) if len(otherGenes)!=0: @@ -1183,31 +1183,31 @@ # the trash file name has the format hgGeneGraph_targetGene_ trashDir = join("..","trash","geneGraph") if not isdir(trashDir): os.mkdir(trashDir) stateStr = makeSelfUrl({}) stateStr += alg stateHash = saltedHash(stateStr, length=20) tmpName = join(trashDir, "%s_%s.dot" % (targetGene, stateHash)) if format=="json": jsonStr = json.dumps(weightedLinks) if len(weightedLinks)==0: - geneName = cgi.escape(cgiString("gene")) + geneName = html.escape(cgiString("gene")) selfLink = makeSelfLink("Remove all filters", {"supportLevel": None}) errAbort("Sorry, there are no direct interactions with %s that fulfill your " "filter criteria. %s" % (geneName, selfLink)) allGenes = set() sifLines = [] for linkRow in weightedLinks: gene1, gene2 = linkRow[:2] allGenes.add(gene1) allGenes.add(gene2) if format=="sif": sifLines.append("%s pp %s" % (gene1, gene2)) if format in ["sif", "json"]: printHttpHead(format) if format=="sif": @@ -1912,41 +1912,44 @@ #def openSqlite(dbName): #" opens sqlite database and have it return structs (=namedtuples) " #tryCount = retries #con = None #con = sqlite3.connect(dbName, timeout=20) #con.row_factory = namedtuple_factory ##con.row_factory = sqlite3.Row def removeInteraction(param, captcha, comment): fields = param.split(":") if len(fields)!=2: errAbort( "illegal CGI parameter") causeGene, themeGene = fields - ip = cgi.escape(os.environ["REMOTE_ADDR"]) + ip = html.escape(os.environ["REMOTE_ADDR"]) conn = hConnectCentral() if not sqlTableExists(conn, "ggFeedback"): - sqlQuery(conn, "CREATE TABLE ggFeedback (causeGene varchar(255), themeGene varchar(255), pmid varchar(255), " \ + # sqlQuery() reads cursor.description, which is None after a statement that returns no + # rows, so it cannot run a CREATE + sqlUpdate(conn, "CREATE TABLE ggFeedback (causeGene varchar(255), themeGene varchar(255), pmid varchar(255), " \ "comment varchar(10000), time TIMESTAMP, ip varchar(255), INDEX allIdx (causeGene, themeGene, pmid));") - cur = conn.cursor() - cur.execute("INSERT INTO ggFeedback VALUES (%s, %s, %s, NOW(), %s, %s)", (causeGene, themeGene, 0, comment, ip)) - cur.close() - conn.close() + # name the columns: the positional version put NOW() into "comment" and the comment text + # into "time". sqlUpdate() commits, which a plain cursor.execute() does not, so nothing + # written here used to survive the end of the request. + sqlUpdate(conn, "INSERT INTO ggFeedback (causeGene, themeGene, pmid, comment, time, ip) " + "VALUES (%s, %s, %s, %s, NOW(), %s)", (causeGene, themeGene, 0, comment, ip)) print ("Interaction successfully removed.

") lastGene = getCgiVar("lastGene") linkUrl = makeSelfUrl({"gene":None, "link":"%s:%s" % (causeGene, themeGene), "lastGene":lastGene}) print(('Return to the Interaction page

' % linkUrl)) def parseGraphArgs(): " get the arguments to build a graph from the CGI parameters " gene = getCgiVar("gene") if gene is None: gene = "MTOR" gene = gene.rstrip(":") gene = gene.split()[0] gene = gene.upper()