pythonforbio.
[WASM idle]
Library Overview06/06

biocommons hgvs

Starting sandbox…
Beginnerlesson

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. to g. 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.5 and NM_000546.6 can have different coordinates. Never strip the version.
  • A variant is meaningless without its reference. c.215C>G alone is not a variant; NM_000546.6:c.215C>G is.
  • 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

lesson example

No output yet — run the code to populate this drawer.

Seabornend of path