HGVS Nomenclature11/11
Starting sandbox…
Beginner·4 graded tests

HGVS Easy

hgvs easy

Core Principle: The hgvs.easy module provides initialized parser, validator, and mapper singletons connected to public remote UTA endpoints for exploratory analysis and interactive prototyping.

The fastest possible start

from hgvs.easy import *

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)      # NC_000017.11:g.7676154G>C
print(var_p)      # NP_000537.3:p.(Pro72Arg)

Three lines from a string to genomic and protein coordinates, with no database setup. That is genuinely useful for learning and for one-off lookups.

What you get

[ ]python
out1ms

This code completed without producing output.

Name What
parser a Parser instance
hdp a UTA data provider, connected to the public remote instance
am37 AssemblyMapper for GRCh37
am38 AssemblyMapper for GRCh38
hn a Normalizer
hv a Validator
parse, normalize, validate, g_to_t, t_to_p, ... module-level function wrappers

The shell

hgvs-shell

Drops you into an interactive Python session with from hgvs.easy import * already executed. Genuinely the fastest way to explore the library or check a single variant.

>>> var = parse("NM_000546.6:c.215C>G")
>>> am38.relevant_transcripts(am38.c_to_g(var))
>>> normalize(var)
>>> validate(var)

Common one-liners

[2]python
out1ms
True

That GRCh37/GRCh38 pair is worth running once yourself. The same variant, two assemblies, ~97,000 bp apart in coordinates. It makes concrete why a genomic position without an assembly is meaningless.

Configuration

export UTA_DB_URL=postgresql://anonymous@localhost:5432/uta/uta_20210129
export HGVS_SEQREPO_DIR=/usr/local/share/seqrepo/2024-02-20

hgvs.easy honours these, so you can point it at local data sources and keep the convenient API. That combination: easy's brevity with local data: is a reasonable setup for interactive work.

When not to use it

hgvs.easy connects to the public remote UTA at import time. That has consequences:

  • Importing is slow: it opens a network database connection.
  • Every operation is a network round-trip unless you have set the environment variables.
  • The remote service is rate-limited and shared. Batch work through it is slow for you and antisocial to everyone else.
  • from hgvs.easy import * dumps a dozen names into your namespace, including parse and validate, which collide with common names.
  • The data version is whatever the remote happens to be, so results are not reproducible.

The explicit form for pipelines

import hgvs.parser
import hgvs.dataproviders.uta
import hgvs.assemblymapper
import hgvs.normalizer
import hgvs.validator

hp  = hgvs.parser.Parser()
hdp = hgvs.dataproviders.uta.connect(pooling=True)
am  = hgvs.assemblymapper.AssemblyMapper(
    hdp,
    assembly_name="GRCh38",
    alt_aln_method="splign",
    replace_reference=True,
    normalize=True,
)
hn = hgvs.normalizer.Normalizer(hdp)
hv = hgvs.validator.Validator(hdp)

print(f"UTA: {hdp.data_version()}")        # ← record this

More lines, but you get: explicit configuration, a recorded data version, connection pooling, no namespace pollution, and control over alt_aln_method and replace_reference.

Build these objects once, at module level or in a factory, and pass them down. Constructing AssemblyMapper per variant is the most common performance mistake in hgvs code.

from functools import lru_cache

@lru_cache(maxsize=1)
def get_tools(assembly="GRCh38"):
    hdp = hgvs.dataproviders.uta.connect(pooling=True)
    return (hgvs.parser.Parser(),
            hgvs.assemblymapper.AssemblyMapper(hdp, assembly_name=assembly,
                                               alt_aln_method="splign",
                                               replace_reference=True),
            hgvs.normalizer.Normalizer(hdp),
            hgvs.validator.Validator(hdp))

The progression

  1. Learning: hgvs-shell and hgvs.easy. No setup, immediate feedback.
  2. Prototyping: hgvs.easy with UTA_DB_URL and HGVS_SEQREPO_DIR pointed at local data.
  3. Production: explicit construction, local data, recorded versions, cached objects, structured error handling.

Do not skip step 1. Understanding what the library does is easier when setup is not in the way.

Common mistakes

  • Using hgvs.easy in a pipeline against the remote instance.
  • from hgvs.easy import * shadowing parse, validate, normalize.
  • Not recording the data version.
  • Reconstructing tools per variant.
  • Assuming am38 means your data is GRCh38: it means the mapper targets GRCh38. If your input coordinates are GRCh37, you need am37 or a liftover.
  • Forgetting hgvs.easy respects the env vars and setting up local data but never using it.

See also

biocommons hgvs · AssemblyMapper · UTA and Data Providers · SeqRepo · hgvs Parser · Applied - Variant Normalization Pipeline

Exercise

hgvs.easy gives you c_to_p in one call, but the package is not available in this sandbox, so you will build the codon table yourself and apply it to a coding sequence supplied on stdin. That is precisely what am.c_to_p does for a substitution: index the codon, retranslate it, and wrap the answer in parentheses because it is a prediction, not an observation.

Write predict(text), taking the whole of stdin and returning a list of output lines. Above a line -- stdin gives cds <sequence>, which begins at the A of the initiator ATG, and protein <accession>. Below it come c. substitutions. For each one emit <variant> <protein_accession>:p.(<consequence>) where the consequence is <Ref><n>= when the amino acid is unchanged, <Ref><n>Ter when the new codon is a stop, and <Ref><n><Alt> otherwise, using three-letter codes and numbering residues from 1. Emit <variant> out_of_range when the position lies outside the coding sequence, <variant> ref_mismatch when the stated reference base disagrees with the sequence, and <variant> unsupported for anything that is not a substitution. Finish with predicted=<n> counting the variants that produced a protein consequence; that line is the only output when no variants follow the --.

The lesson's worked example is NM_000546.6:c.215C>G on TP53, which predicts NP_000537.3:p.(Pro72Arg). The transcript in the test data is synthetic so that you can check the arithmetic without a sequence database, but residue 72 is a proline there too, so the same input gives the same consequence.

exerciseEvery submission runs independently in a fresh namespace.

Test cases · 4

#viainputexpected stdout
1stdincds ATGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCCCCGAGCCTACCTTGAAAGTCCGCTGGCAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGTGA protein NP_999999.1 -- NM_999999.1:c.215C>G NM_999999.1:c.216C>T NM_999999.1:c.241C>T NM_999999.1:c.1A>G NM_999999.1:c.215A>G NM_999999.1:c.215C>G NP_999999.1:p.(Pro72Arg) NM_999999.1:c.216C>T NP_999999.1:p.(Pro72=) NM_999999.1:c.241C>T NP_999999.1:p.(Gln81Ter) NM_999999.1:c.1A>G NP_999999.1:p.(Met1Val) NM_999999.1:c.215A>G ref_mismatch predicted=4
2stdincds ATGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCCCCGAGCCTACCTTGAAAGTCCGCTGGCAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGAAAGTCCGCTGGGAGCCTACCTTGTGA protein NP_999999.1 -- NM_999999.1:c.9999C>G NM_999999.1:c.303A>G NM_999999.1:c.304C>G NM_999999.1:c.215del NM_999999.1:c.-24A>G NM_999999.1:c.9999C>G out_of_range NM_999999.1:c.303A>G NP_999999.1:p.(Ter101Trp) NM_999999.1:c.304C>G out_of_range NM_999999.1:c.215del unsupported NM_999999.1:c.-24A>G unsupported predicted=1
3stdincds ATGCCCTGA protein NP_000001.1 -- NM_000001.1:c.5C>G NM_000001.1:c.6C>T NM_000001.1:c.4C>T NM_000001.1:c.5C>G NP_000001.1:p.(Pro2Arg) NM_000001.1:c.6C>T NP_000001.1:p.(Pro2=) NM_000001.1:c.4C>T NP_000001.1:p.(Pro2Ser) predicted=3
4stdincds ATGCCCTGA protein NP_000001.1 -- predicted=0

Hints · 3

01Hint

Residue number is (pos - 1) // 3 + 1 and the base within the codon is (pos - 1) % 3.

02Hint

Build the table from the 64-character standard code indexed by 16*b1 + 4*b2 + b3 with bases ordered TCAG.

03Hint

Check ref_mismatch against the sequence before you translate, or you will confidently report the wrong reference residue.