7a55a823e6fe9bc65ac0ecf5b6576e90fda64bcb hiram Wed Aug 12 16:10:27 2026 -0700 fixups per code review refs #38005 diff --git src/hg/utils/automation/gffAttrsToIx.py src/hg/utils/automation/gffAttrsToIx.py index 27c074d21e6..1e590bf7c22 100755 --- src/hg/utils/automation/gffAttrsToIx.py +++ src/hg/utils/automation/gffAttrsToIx.py @@ -14,30 +14,31 @@ geneAttrs.ncbi.txt): when gff3ToGenePred is run with -useName, the attribute rows for an mRNA record are written keyed by the *resolved* genePred item name (e.g. "GCSAML"), not the raw GFF3 "rna-..." id. That means the first column of attrsOut already matches the genePred name field directly -- no id-juggling needed to join transcript attributes. The gene-level block, by contrast, stays keyed by the raw "gene-..." id, and is reached from the transcript block via its "Parent" attribute. usage: gffAttrsToIx.py asmId.geneAttrs.ncbi.txt asmId.ncbiGene.genePred.gz \ | sort -u > extra.ix.txt """ import sys import gzip +import re from collections import defaultdict # GFF3/attrsOut attribute names worth surfacing as search terms, and # whether they come off the transcript (rna-) record or the gene record. TX_ATTRS = ("product", "Note", "gene") GENE_ATTRS = ("Name", "description", "gene", "gene_synonym", "locus_tag") # Dbxref source prefixes worth stripping so the bare id becomes searchable # (same list gff3ToGenePred/ncbiRefSeqOtherAttrs.pl already know about). DBXREF_SOURCES = ("GeneID", "MIM", "HGNC", "MGI", "WormBase", "XenBase", "BGD", "RGD", "SGD", "ZFIN", "FlyBase", "miRBase", "NCBIOrtholog") def openMaybeGz(path): @@ -53,56 +54,56 @@ continue parts = line.rstrip("\n").split("\t") if len(parts) < 3: continue itemId, attr, val = parts[0], parts[1], parts[2] attrs[itemId][attr].append(val) return attrs def genePredNames(gpFile): """distinct values from column 1 (name) of the genePred""" names = [] seen = set() with openMaybeGz(gpFile) as fh: for line in fh: - if line.startswith("#"): + line = line.strip() + if not line or line.startswith("#"): continue name = line.split(None, 1)[0] if name not in seen: seen.add(name) names.append(name) return names def dbxrefTerms(values): terms = [] for val in values: for xref in val.split(","): bare = xref for source in DBXREF_SOURCES: prefix = source + ":" if xref.lower().startswith(prefix.lower()): bare = xref[len(prefix):] break terms.append(bare) return terms def noSuffix(name): """strip a trailing .NNN version suffix, same trick as gpToIx.pl""" - import re stripped = re.sub(r"\.[0-9]+$", "", name) return stripped if stripped != name else None def buildAliases(name, attrs): txRec = attrs.get(name, {}) terms = [] for attr in TX_ATTRS: terms.extend(txRec.get(attr, [])) terms.extend(dbxrefTerms(txRec.get("Dbxref", []))) for geneId in txRec.get("Parent", []): geneRec = attrs.get(geneId, {}) for attr in GENE_ATTRS: