Load a monomer library¶
Every HELM string is only meaningful against a monomer library: the dictionary that
says what moe, Nle or beta-Ala actually are (their SMILES, natural analog, and
R-group connection points). HELMshaker can source that library from several places:
| Source | Method | When to use |
|---|---|---|
| In-memory records | MonomerLibrary.from_records(records) |
Records you already have or generate (manual injection) |
| A JSON file | MonomerLibrary().load_from_file(path) |
A curated/custom dictionary on disk |
| TMR (direct) | MonomerLibrary.from_tmr(base_url, dictionary, ...) |
Roche monomer dictionaries from TMR via Gravitee |
| Forge | MonomerLibrary.from_forge(base_url, ...) |
Monomer dictionaries served by Forge |
| Local cache | MonomerLibrary.from_cache(dictionary, version) |
A previously pulled dictionary, offline |
HELMshaker ships no bundled monomers.
MonomerLibrary()starts empty; you always supply monomers by manual injection (records / a JSON file) or by pulling from TMR. Reading a molecule without a library raisesMonomerLibraryError.
This notebook walks through each source, shows runnable cells for logging in, pulling, and displaying a TMR dictionary, how to build HELMs with a chosen library (including peptides via the sequence grammar), and how to pin the source dictionary into a HELM string so it can be validated against the exact same version later.
Kernel setup: run this notebook with the project's environment (
uv run jupyter lab, then pick the.venvkernel). Ifimport helmshakerfails withModuleNotFoundError, install the package into the env once withuv pip install -e .and restart the kernel.
1. Start empty, inject monomers¶
MonomerLibrary() is empty; there is no bundled default. The simplest way to populate
one is from_records: a list of HELM-style records (symbol, polymerType,
naturalAnalog, smiles, rgroups). We build a small demo library here and reuse it
below to build HELMs offline; in practice you'd pull the real dictionary from TMR (§4).
from helmshaker import Molecule, MonomerLibrary
empty = MonomerLibrary()
print("empty library:", len(empty), "monomers")
def _pep(sym, analog, rlabels):
return {"symbol": sym, "name": sym, "monomerType": "Backbone", "polymerType": "PEPTIDE",
"naturalAnalog": analog, "smiles": "[*:1]N[C@@H](C)C([*:2])=O",
"rgroups": [{"label": l, "capGroupSmiles": "[*][H]"} for l in rlabels]}
def _rna(sym, analog, mtype):
return {"symbol": sym, "name": sym, "monomerType": mtype, "polymerType": "RNA",
"naturalAnalog": analog, "smiles": "OC[C@H]1O[C@H](n)C[C@@H]1O",
"rgroups": [{"label": "R1", "capGroupSmiles": "[*][H]"}, {"label": "R2", "capGroupSmiles": "[*][H]"}]}
demo = MonomerLibrary.from_records([
_pep("C", "C", ["R1", "R2", "R3"]), _pep("A", "A", ["R1", "R2"]), _pep("Nle", "L", ["R1", "R2"]),
_pep("R", "R", ["R1", "R2"]), _pep("N", "N", ["R1", "R2"]),
_rna("moe", "r", "Backbone"), _rna("sp", "p", "Backbone"),
_rna("A", "A", "Branch"), _rna("U", "U", "Branch"), _rna("G", "G", "Branch"),
])
print("demo library:", len(demo), "monomers; symbols:", demo.symbols())
empty library: 0 monomers demo library: 9 monomers; symbols: ['A', 'C', 'G', 'N', 'Nle', 'R', 'U', 'moe', 'sp']
2. A custom library from a JSON file¶
A monomer dictionary is a list of HELM-style records (symbol, name, smiles,
rgroups, naturalAnalog, ...). Load one from disk with load_from_file, which merges
into the (empty) library.
# A minimal dictionary written to disk for illustration.
import json, tempfile, os
records = [
{
"symbol": "beta-Ala",
"name": "beta-Alanine",
"monomerType": "Backbone",
"polymerType": "PEPTIDE",
"naturalAnalog": "A",
"smiles": "[*:1]NCCC(=O)[*:2]",
"rgroups": [
{"label": "R1", "capGroupSmiles": "[*:1][H]"},
{"label": "R2", "capGroupSmiles": "[*:2]O"},
],
"uuid": "example-0001",
}
]
path = os.path.join(tempfile.mkdtemp(), "my_library.json")
with open(path, "w") as fh:
json.dump(records, fh)
custom = MonomerLibrary()
custom.load_from_file(path)
print(f"custom library: {len(custom)} monomers")
print("beta-Ala connection points:", custom.get_connection_points("beta-Ala"))
custom library: 1 monomers
beta-Ala connection points: {'R1', 'R2'}
3. Records already in memory¶
If you already have the records (from an API response, a database, a generated set),
build the library directly with from_records, with no file involved. This is exactly the
schema the remote paths map into internally.
records_lib = MonomerLibrary.from_records(records)
print(len(records_lib), "monomer:", "beta-Ala" in records_lib)
1 monomer: True
4. Pulling a library from TMR¶
MonomerLibrary.from_tmr fetches a dictionary directly from TMR through the pRED
Gravitee gateway. It resolves the dictionary name to its pk, fetches the monomer set,
maps each TMR MonomerResponse into a HELMshaker record, and caches the result on disk.
The gateway's Janus plan authorizes the request from your access token, because it reads
the client_id claim to match your Gravitee subscription, and that claim is only on the
access token (the id token gets a 401). Your Janus client must be subscribed to the
TMR API's Janus plan. The gateway also presents an internal TLS certificate, so
verification may need a CA bundle or to be disabled (verify=False).
The cells below are runnable end-to-end: setup → configure → authenticate → list →
pull → display. Unlike the CLI, from_tmr does not resolve the token for you, so we
pass the access token explicitly.
Setup: the [remote] extra¶
The network path needs httpx (the [remote] extra). Install it into this kernel once;
skip if it's already present. (Use !uv pip install rather than %pip, because a uv-managed
.venv has no pip module.)
Not run when these docs are built. This step needs a Janus login and network access to TMR, so the docs build skips it. Copy it into your own session to run it.
# One-time: install the HTTP client used for the network calls.
uv pip install -q httpx
Configure¶
JANUS_ENV picks the login issuer. Note the tmr-tst gateway's Janus plan trusts the
prod issuer, so use stable to reach tmr-tst (the beta/-tst token is rejected
by the plan). CLIENT_ID can stay blank, and login then uses the built-in per-env default (the
Janus client subscribed to the TMR API).
VERIFY. The gateway presents an internal certificate chain, so point at a corporate CA
bundle ($HELMSHAKER_CA_BUNDLE) or set VERIFY = False.
import os
TMR_URL = "https://api.core.minerva.roche.com/gateway/tmr-tst"
DICTIONARY = "TMR Amino Acids" # a real tmr-tst dictionary (run the "list" cell below to see names)
VERSION = None # None = latest, or a specific version
JANUS_ENV = "stable" # tmr-tst's Janus plan trusts the prod issuer -> use stable
CLIENT_ID = os.environ.get("HELMSHAKER_JANUS_CLIENT_ID", "") # optional; blank uses the built-in default
VERIFY = os.environ.get("HELMSHAKER_CA_BUNDLE") or False # CA-bundle path, or False to skip TLS verify
Authenticate¶
Reuse a cached login / $HELMSHAKER_TOKEN if present; otherwise start the
device-authorization flow: HELMshaker prints a short code and a URL. Open it in any
browser (even on your phone), sign in, and approve. No local server, port, or redirect is
needed, so it also works over SSH / in containers. The alternatives (paste a token, or log
in from the shell) are shown commented below.
Not run when these docs are built. This step needs a Janus login and network access to TMR, so the docs build skips it. Copy it into your own session to run it.
from helmshaker.auth import resolve_token, login_device
# TMR's Gravitee Janus plan reads the client_id claim, so we need the ACCESS token.
token = resolve_token(prefer="access")
if not token:
# Device flow: prints a URL + code to approve in any browser, then caches the token.
login_device(client_id=CLIENT_ID or None, janus_env=JANUS_ENV)
token = resolve_token(prefer="access")
print("access token acquired:", bool(token))
# --- Alternatives -----------------------------------------------------------------
# Log in from the shell instead (device flow by default):
# !helmshaker login --janus-env stable
Which dictionaries exist?¶
List the monomer dictionaries available in TMR so you can pick a real DICTIONARY name
(tmr-tst currently has HELMCoreLibrary and TMR Amino Acids). From the shell this is
helmshaker library dictionaries --tmr <url>.
Not run when these docs are built. This step needs a Janus login and network access to TMR, so the docs build skips it. Copy it into your own session to run it.
from helmshaker.remote_tmr import list_dictionaries
for d in list_dictionaries(TMR_URL, token=token, verify=VERIFY):
print(f"{d['name']:<28} version={d.get('current_version')} pk={d['pk']}")
Pull the dictionary¶
The result is a normal MonomerLibrary. fallback=False makes a failed fetch raise
instead of falling back to the cache, so you never mistake a 401/TLS error for a real
pull. After the first success the response is cached on disk, so re-running (or
MonomerLibrary.from_cache(DICTIONARY, VERSION)) serves it offline.
Not run when these docs are built. This step needs a Janus login and network access to TMR, so the docs build skips it. Copy it into your own session to run it.
library = MonomerLibrary.from_tmr(
base_url=TMR_URL,
dictionary=DICTIONARY,
version=VERSION,
token=token,
verify=VERIFY,
fallback=False,
)
print(f"pulled {len(library)} monomers from {DICTIONARY!r}")
Display the pulled dictionary¶
symbols() lists what's in the library; get_definition(symbol) returns the full record
(SMILES, natural analog, R-group attachment points).
Not run when these docs are built. This step needs a Janus login and network access to TMR, so the docs build skips it. Copy it into your own session to run it.
syms = library.symbols()
print(f"{len(syms)} symbols; first 20:")
print(syms[:20])
print("\nsample records:")
for sym in syms[:5]:
d = library.get_definition(sym)
rgroups = [a.label for a in d.attachment_points]
print(f" {d.symbol:<12} analog={d.natural_analog} type={d.polymer_type} R-groups={rgroups}")
In pipelines (non-interactive)¶
CI and batch jobs can't open a browser. Two patterns, in order of reproducibility:
Token via environment variable. The runner injects a Janus token as
$HELMSHAKER_TOKEN; the authenticate cell above then resolves it with no browser step, and the pull cell runs unchanged.Pre-warmed cache (most reproducible). A setup step pulls once and the job persists
$HELMSHAKER_CACHE_DIR(default~/.cache/helmshaker); the run step then reads it fully offline, no token required:# setup step (has network + token) export HELMSHAKER_CACHE_DIR=/opt/helmshaker-cache helmshaker library pull --tmr "$TMR_URL" --dictionary peptides --version 1.2.0
# run step (offline): served from the persisted cache library = MonomerLibrary.from_cache("peptides", "1.2.0")
Pair this with the {lib=name@version} pin (section 6) so every HELM records the exact
dictionary it was built against.
Offline demo: the TMR → HELMshaker mapping¶
If you don't have credentials handy, this reproduces what from_tmr does internally: a
TMR MonomerResponse (snake_case, R-group-labelled core_structure) is mapped into a
HELMshaker record and loaded. Fully offline.
# A TMR MonomerResponse object, as returned by GET /api/monomers/
tmr_monomer = {
"symbol": "beta-Ala",
"name": "beta-Alanine",
"monomer_type": "Backbone",
"polymer_type": "PEPTIDE",
"natural_analogon": "A",
"core_structure": "[*:1]NCCC(=O)[*:2]",
"attachment_smiles": ["[*:1][H]", "[*:2]O"],
"tmr_id": "tmr-0001",
}
# The same mapping from_tmr applies before caching (see helmshaker.remote_tmr):
def record_from_tmr(m):
caps = m.get("attachment_smiles") or []
return {
"symbol": m.get("symbol"),
"name": m.get("name"),
"monomerType": m.get("monomer_type"),
"polymerType": m.get("polymer_type"),
"smiles": m.get("core_structure"),
"naturalAnalog": m.get("natural_analogon"),
"uuid": m.get("tmr_id") or m.get("monomer_pk") or "",
"rgroups": [
{"label": f"R{i+1}", "capGroupSmiles": cap}
for i, cap in enumerate(caps)
],
}
tmr_like = MonomerLibrary.from_records([record_from_tmr(tmr_monomer)])
print("mapped record ->", tmr_like.symbols(), tmr_like.get_connection_points("beta-Ala"))
mapped record -> ['beta-Ala'] {'R1', 'R2'}
5. Building HELMs with a chosen library¶
Any library you obtain above can be passed as monomer_library= when reading or building
molecules. When strict reading is on (the default for from_helm), monomers absent from
the library are rejected, so the library you choose defines what is valid.
# Read a HELM against the demo library from §1 (in practice, a pulled TMR library).
oligo = Molecule.from_helm(
"RNA1{[moe](A)[sp].[moe](U)[sp].[moe](G)}$$$$V2.0",
monomer_library=demo,
)
print("oligo FASTA:", oligo.to_fasta())
oligo FASTA: AUG
Peptides via the sequence grammar¶
Molecule.from_peptide_sequence builds a HELM from a peptide sequence string using the
peptide sequence grammar, resolving crosslinks and connection points against the
library you pass. It accepts modified-FASTA (CAXRN) or dot-delimited sequences
(C.A.Nle.R.N), X{n}=code modifiers, N/C caps, and crosslink notation.
# A cyclic peptide: disulfide between residues 1 and 5.
pep = Molecule.from_peptide_sequence(
"CAAAC",
crosslinks="C:1-C:5",
monomer_library=demo,
)
print("HELM: ", pep.to_helm())
print("cyclic:", pep.data.is_cyclic("PEPTIDE1"))
HELM: PEPTIDE1{C.A.A.A.C}$PEPTIDE1,PEPTIDE1,1:R3-5:R3$$$V2.0
cyclic: True
# Non-natural monomers: dot-delimited, and via an X{n}=code modifier.
from helmshaker.peptide_grammar import parse_peptide_sequence
print(parse_peptide_sequence("C.A.Nle.R.N", monomer_library=demo).helm)
mod = Molecule.from_peptide_sequence("CAXRN", modifiers="X3=Nle", monomer_library=demo)
print("HELM: ", mod.to_helm())
print("natural seq: ", mod.data.get_natural_sequence("PEPTIDE1"))
PEPTIDE1{C.A.[Nle].R.N}$$$$V2.0
HELM: PEPTIDE1{C.A.[Nle].R.N}$$$$V2.0
natural seq: CALRN
The grammar needs the referenced monomers (e.g.
Nle, or yourbeta-Ala) in the supplied library. Pull the right dictionary from TMR first (section 4), then pass it asmonomer_library=here so custom monomers resolve, for exampleMolecule.from_peptide_sequence("C.A.beta-Ala.R", monomer_library=library).
6. Pinning the source library into a HELM¶
A HELM string alone doesn't record which dictionary version it was built against.
HELMshaker can write a {lib=name@version} marker into the HELM extended-annotations
field so the source dictionary travels with the string.
from helmshaker.annotations import annotate_helm, read_lib_annotation
helm = "PEPTIDE1{C.A.A.A.C}$$$$V2.0"
pinned = annotate_helm(helm, "peptides@1.2.0")
print("pinned: ", pinned)
print("read back:", read_lib_annotation(pinned))
pinned: PEPTIDE1{C.A.A.A.C}$$${lib=peptides@1.2.0}$V2.0
read back: peptides@1.2.0
Reading a pinned HELM exposes the reference via Molecule.library_ref. With
resolve_pinned=True and no explicit monomer_library, from_helm loads that exact
dictionary from the local cache (no network), tying the pipeline's pre-warmed cache
(section 4) to reproducible validation. On a cache miss with no library it raises
MonomerLibraryError (there is no bundled fallback). Passing a monomer_library= always
takes precedence over the pin.
# The library_ref is read from the annotation regardless of which library you supply.
m = Molecule.from_helm(pinned, monomer_library=demo)
print("library_ref:", m.library_ref)
# With resolve_pinned=True and no explicit library, HELMshaker loads "peptides@1.2.0"
# from the local cache (populate it first via `helmshaker library pull` or from_tmr):
# m2 = Molecule.from_helm(pinned, resolve_pinned=True)
# print("library_ref:", m2.library_ref)
library_ref: peptides@1.2.0
Summary¶
- Choose a library source: in-memory records, a JSON file, TMR, Forge, or the local cache.
- Interactive TMR pull: configure → authenticate (
resolve_token()/login_device) →MonomerLibrary.from_tmr(base_url, dictionary=..., token=token)→ inspect withsymbols()/get_definition(). - Pipeline TMR pull: inject
$HELMSHAKER_TOKEN, or pre-warm and persist$HELMSHAKER_CACHE_DIRand read offline withfrom_cache. - Pass any library as
monomer_library=to build/validate HELMs, including peptides viafrom_peptide_sequenceand the sequence grammar. - Pin the source dictionary into a HELM with
{lib=name@version}and re-resolve it later withfrom_helm(..., resolve_pinned=True).