biocommons hgvs
In one line: parse, normalize, validate and project sequence variants written in HGVS nomenclature — correctly, which is harder than it looks.
Version at time of writing: 1.5.7 (Mar 2026) · Python ≥3.10 · pip install hgvs · pin as hgvs>=1.5,<1.6
The mental model
Four services, layered over two data sources.
"NM_000546.6:c.215C>G"
│
┌───────▼────────┐
│ Parser │ formal PEG grammar → SequenceVariant object
└───────┬────────┘
│
┌────────────┼────────────┬──────────────┐
▼ ▼ ▼ ▼
Normalizer Validator AssemblyMapper Formatter
(canonical (is it (g. ↔ c. ↔ p.) (back to string)
form) real?)
└────────────┴────────────┴──────────────┘
│
┌───────▼────────┐
│ Data provider │
└───┬────────┬───┘
▼ ▼
UTA SeqRepo
(transcript (the actual
alignments) sequences)
Parsing is free and offline. Everything else — normalization, validation, projection — needs sequence and alignment data, because you cannot know whether c.215C>G is valid without knowing what base 215 actually is.
Why this library exists
Variant strings look like they can be handled with regex and arithmetic. They cannot. The reasons:
- Ambiguity. In a run of identical bases, a deletion has several equally valid coordinate representations. Only one is canonical. VCF picks the leftmost; HGVS picks the 3'-most on the reference sequence's own strand. Two "different" variants can be the same event.
- Transcript geometry. Converting
c.tog.requires knowing every exon boundary, and whether the transcript is on the reverse strand, and whether the alignment has indels relative to the genome. - Non-linear coordinates.
c.numbering has no zero, uses negative numbers for 5'UTR,*for 3'UTR, and+/-offsets for intronic positions. - Clinical stakes. A misprojected variant in a diagnostic report is a patient-safety event.
Anyone writing re.match(r"c\.(\d+)([ACGT])>([ACGT])", s) is building a bug factory. Use this library.
Core notes
Understand the standard first HGVS Nomenclature · Coordinate Systems
The objects hgvs Parser · SequenceVariant
The data behind it UTA and Data Providers · SeqRepo · hgvs easy
The operations hgvs Normalizer · hgvs Validator · AssemblyMapper · Projection c to g to p
Survival guide hgvs Pitfalls
Applied
Applied - Variant Normalization Pipeline · Applied - Annotating a Variant Table
Install and first run
pip install "hgvs>=1.5,<1.6"
The fastest possible start uses the public remote UTA instance — no database setup:
from hgvs.easy import parse, normalize, validate, am38, g_to_t, t_to_p
var_c = parse("NM_000546.6:c.215C>G")
var_g = am38.c_to_g(var_c)
var_p = am38.c_to_p(var_c)
print(var_g, var_p)
There is also an hgvs-shell command that drops you into a REPL with all of the above pre-imported.
For anything beyond exploration, run UTA and SeqRepo locally. The remote instance is a shared courtesy service; it is slow, rate-limited, and unsuitable for a pipeline over thousands of variants. See UTA and Data Providers and SeqRepo.
export UTA_DB_URL=postgresql://anonymous@localhost:5432/uta/uta_20210129
export HGVS_SEQREPO_DIR=/usr/local/share/seqrepo/2024-02-20
The 20% that gets 80% of the work done
import hgvs.parser, hgvs.dataproviders.uta, hgvs.assemblymapper
import hgvs.normalizer, hgvs.validator
hp = hgvs.parser.Parser()
hdp = hgvs.dataproviders.uta.connect()
am = hgvs.assemblymapper.AssemblyMapper(
hdp, assembly_name="GRCh38", alt_aln_method="splign", replace_reference=True
)
hn = hgvs.normalizer.Normalizer(hdp)
hv = hgvs.validator.Validator(hdp)
var_c = hp.parse_hgvs_variant("NM_000546.6:c.215C>G")
hv.validate(var_c) # raises HGVSError subclasses on failure
var_c = hn.normalize(var_c)
var_g = am.c_to_g(var_c)
var_p = am.c_to_p(var_c)
am.relevant_transcripts(var_g) # which transcripts overlap this genomic variant
Build hdp and am once and reuse them. Constructing them per variant re-opens the database connection and destroys performance.
Gotchas that bite newcomers
- Accession versions matter.
NM_000546.5andNM_000546.6can have different coordinates. Never strip the version. - A variant is meaningless without its reference.
c.215C>Galone is not a variant;NM_000546.6:c.215C>Gis. p.variants from projection are predictions, prefixed with()by convention. They are inference, not observation.- Normalization changes with data version. Pin your UTA and SeqRepo snapshots, not just the library. See Applied - Reproducible Environment.
- HGVS and VCF shift indels in opposite directions. Round-tripping without normalization loses variants. See hgvs Pitfalls.
Related tooling worth knowing
- bioutils — biocommons helper library (assembly maps, sequence utilities)
- vrs-python — GA4GH VRS, computed identifiers for variants; the direction the field is heading
- VariantValidator (
openvar/vv_hgvs) — a fork of this library with a web service on top - Mutalyzer, Ensembl VEP — alternative implementations; useful for cross-checking
See also
Biopython · Ecosystem Map · Coordinate Systems · Learning Path