Skip to contents

bioclients (development version)

ClinVar

  • clinvar_classification() gains allele, the protein change you mean, such as "V600E" or "p.Val600Glu" (#40). ClinVar’s search often matches several records, and the first is not always the variant you asked for: rs113488022 lists BRAF V600G before V600E, the HGVS name of V600E lists BRAF I208V first, and rs121913343 lists TP53 R273G before R273C. With allele, the record that has that protein change is returned, or no_data when none has it.
  • The result has a new column, n_matches: how many records fit the request. A value above 1 means the row may be for a different variant from the one you meant. Without allele the first record is still returned, so existing code keeps working.
  • The help page now names the terms that match one record, an SPDI or a VCV accession, and no longer suggests an rsID as the best term.

MyGene

  • mygene_gene() and mygene_genes() now return an Ensembl id for genes that MyGene maps to more than one id, such as PTEN and MUC16. Before this, ensembl_gene was NA for all of them (#39). The extra ids sit on assembly patches or alternate haplotypes, and the one on a reference chromosome is kept. Taking the first id would not work: for HLA-A it is on an alternate haplotype. A gene whose ids all sit off the reference chromosomes, such as HLA-DRB3, gets NA. Lookups that start from ensembl_gene, such as opentargets_gene_diseases() and hpa_gene(), now work for these genes.

bioclients 0.1.1

CRAN release: 2026-09-15

Ensembl VEP

  • Results are matched back on the echoed input line and on vcf_string rather than on a key rebuilt from the reported coordinates. VEP left-trims and renumbers indels, so the rebuilt key matched only SNVs and every indel came back as an empty row.
  • vep_variants() takes an options list of request flags, defaulting to vep_default_options(). dbNSFP is refused.
  • vep_parse_element() adds transcript, gene_id, biotype, hgvsc, hgvsp, canonical, codons, amino_acids, cadd_phred, cadd_raw, revel, the four spliceai_ds_* scores, spliceai_max and lof.
  • New vep_parse_colocated() reads the dbSNP record beside a variant: rsid, gnomAD genome and exome frequencies with their per-population maximum, and the clinical significance of the allele asked. vep_parse_batch() carries these columns.
  • New vep_variants_all() takes any number of variants, chunks them at the 200 VEP accepts per POST and dispatches the chunks through biohttp::post_json_many(). One row per input in input order; a failed chunk becomes rows of NA carrying the envelope status in status.
  • vep_variants_all() puts the input in genome order and sends a repeated variant once, before chunking, rather than once per occurrence. A duplicate now always carries the same answer at every position it was asked at, which was not guaranteed before when duplicates could land in different chunks and see different transient failures. chunk_size and the row count and order the contract promises are unchanged. It also applies a default throttle (one request per second) so an unpaced burst does not trip Ensembl’s rate limiter; pass throttle = NULL to disable it.

gnomAD

  • New gnomad_frequency_by_id() looks a variant up by chrom-pos-ref-alt, which names one allele where an rsID names a site, and gnomad_frequencies() does the same for many ids, alias-batched and chunked like gnomad_constraints(). gnomad_variant_id() builds the id. The flat row from gnomad_parse_variant() carries exome and genome af, ac, an and nhomalt, a derived grpmax, faf95 and filters.
  • The 25-alias cost cap was re-verified against the live API for the variant query. The cost is one per alias whatever the selection set.

ClinVar

  • clinvar_classification() throttles by default at the documented E-utilities rate, 3 requests a second or 10 when NCBI_API_KEY is set, and sends tool and email read from BIOHTTP_CALLER_IDENTITY and BIOHTTP_CONTACT_EMAIL when they are set.

MyGene

Packaging

Everything below is for the CRAN submission. Nothing here changes how the package behaves, and no function’s arguments or output shape moved.

  • Every example that calls a service now runs under \donttest{} rather than \dontrun{}, 56 of them. \dontrun{} is for an example that genuinely cannot be executed, which none of these are: they are keyless public requests, a client returns an envelope rather than raising, and biohttp’s disk cache is off unless a caller turns it on. So an example run with no network prints NULL and writes nothing.

  • Every help page now cites the service behind it. The 29 service files each carry the canonical publication with a DOI and the service’s own documentation URL, and the other topics in the file inherit it, so all 134 exported topics have a References section. Each DOI was resolved through CrossRef and checked against the title, author and pagination written here. uniprot_features() carries two, because it calls the EBI Proteins API on a different host to the rest of the file and both are worth naming.

  • The Description field cites five of those references, for Ensembl, UniProt, gnomAD, Open Targets and AlphaFold DB, in the form CRAN asks for.

  • biohttp is on CRAN now, at 0.1.2. So the Remotes: line is gone and we ask for biohttp (>= 0.1.2) instead. I ran the tests against the CRAN build rather than the newer one on GitHub, to be sure nothing here needs a version CRAN cannot give you.

  • cran-comments.md records the submission notes.

bioclients 0.1.0

First release. 29 clients, 125 exported functions, and the behaviour they depend on checked against the live services rather than only against stored responses. 28 of the 29 confirmed; see “Known limits” for the one that did not, which was down at the time rather than wrong.

The envelope contract comes from biohttp and is fixed. What can still move is the shape of a parser’s output, since a service adding a field is a reason to carry it. Anything that changes an existing column is a breaking change and gets a version to say so.

What it covers

  • One client per service, 29 of them, covering every web service the app family calls. 125 exported functions in all.
  • Every client is split in two. The parser takes an already parsed response body and returns a tibble, or NULL when there is genuinely nothing there. It touches no network, so it is tested directly against a stored response.
  • The request half calls biohttp, passes a failing envelope straight through, turns a NULL parse into no_data, and wraps a success in ok. No client writes its own tryCatch().
  • Where a service supports it, a batch entry point returns one row per input in input order, with a row of NA for a miss. A shorter table would silently shift every row after it onto the wrong gene.

Fixtures

  • 51 stored response bodies, ported byte for byte out of the apps this package replaces. A test asserts they still match.
  • A ported fixture that needs editing is treated as a signal rather than a chore: it means the parser changed behaviour during the port, and that has to be justified rather than fixed by editing the recording.

Verified service behaviour

These cost real debugging time to establish in the apps and are easy to lose in a port, so each one has a comment at the call site and a test where it is testable.

Everything below has now been confirmed against the live service, not only against a stored response. tests/testthat/test-live.R is where that happens. It calls all 29 services once and then makes the specific assertions the claims below depend on. It never runs on its own, and no CI job turns it on:

BIOCLIENTS_LIVE=true Rscript -e 'devtools::test("pkg-r", filter = "live")'
  • MyVariant silently returns notfound for GRCh38 coordinates when the request omits assembly=hg38. Silent, not an error, which is exactly why it needs a test.
  • Ensembl VEP takes exactly 200 per POST, gnomAD exactly 25 GraphQL aliases, MyVariant 1000 per batch. Verified ceilings, not tuning.
  • IMPC’s genotype-phenotype core has no human_gene_symbol field, so querying it by symbol returns HTTP 200 with zero documents. A miss is no_data and never 0, because a gene never phenotyped and a gene phenotyped with no significant result are not the same answer.
  • Europe PMC and PubTator count zero as an answer, so both come back ok. A gene with no literature is not an outage.
  • GTEx needs its own versioned GENCODE id, from GTEx’s own reference lookup. An Ensembl gene id from anywhere else will not work.
  • MyGene returns the HGNC id under HGNC, upper case. Asking for hgnc returns nothing at all.
  • Monarch wants the CURIE colon unencoded, so HGNC:11998 must reach it as-is rather than as HGNC%3A11998.
  • PanelApp’s index search parameter does not filter, and Reactome answers a gene it does not know with a 404 rather than an empty array.
  • STRING replies with text/json, so the content type check has to be off.
  • Ensembl takes content-type as a query parameter, not a header. Leave it out and Ensembl serves its HTML browser page with HTTP 200, so the call looks like a success until the JSON parser reaches the first tag.
  • On the minus strand, Ensembl numbers exons from the highest coordinate, because they are numbered in transcription order rather than by position.

Credentials

  • clinvar_classification() picks up NCBI_API_KEY from the environment at call time and passes it through biohttp’s secret_query, which attaches it at dispatch. The key never reaches the cache key, a printed request, or an error message. It is optional; without it NCBI allows 3 requests a second instead of 10.

What the live run changed

Four of the five behaviours that had never been checked against a real server held exactly as the port described them: MyGene’s upper case HGNC, Reactome’s 404, PanelApp’s search that does not filter, and Ensembl’s array wrapping a single record.

The fifth did not, and it was the one with an action already written into it. Monarch was being called on two hosts, api-v3.monarchinitiative.org for search and association and api.monarchinitiative.org for the entity route, because that was how the apps did it and nobody had checked whether they were interchangeable. They are. All three routes answer on both, and the entity payload is identical between them down to the order of the ids. So Monarch is one host now, which also means one circuit breaker instead of two. A host going down used to open only half of them.

Known limits

  • biohttp is not on CRAN, so DESCRIPTION carries a Remotes: line.
  • Pharos was returning HTTP 502 from its own gateway throughout the live run, so its probe is the one service the run could not confirm. That is an outage rather than a finding about the port, and the check is left in place to say so the next time it is run.