agvd — Python client for the African Genome Variation Database
agvd is a Python client for the African Genome Variation Database (AGVD) GraphQL API — a typed, paginated, retrying AGVDClient for programmatic use, plus the agvd command-line tool for bulk VCF/table annotation that ships on top of it.
Installation
# The library only — AGVDClient, models, exceptions. Needs just `requests`.
pip install agvd
# + the bulk file-processing CLI (pandas, pysam, openpyxl, tqdm)
pip install "agvd[cli]"
Requires Python 3.10+.
Authentication
Most queries are public. Four operations require a Bearer token: search_variant, the batch tools (cli_variant_search, cli_vcf_filter, file_upload, cli_variant_peek/peek), and create_dataset (which also needs the admin role). Contact agvd@afrigen-d.org for a token.
from agvd import AGVDClient
client = AGVDClient(token="YOUR_TOKEN")
Or set AGVD_TOKEN and omit token=:
export AGVD_TOKEN=YOUR_TOKEN
client = AGVDClient() # picks up AGVD_TOKEN automatically
Public queries work with no token at all — AGVDClient() with nothing configured raises only when you call something that actually needs one, and does it immediately (no network round trip for a 401).
Quickstart
from agvd import AGVDClient
client = AGVDClient(token="YOUR_TOKEN")
# Look up a specific variant (accepts any common id delimiter — see
# "Variant ID format" below).
conn = client.fetch_variant(variant_id=["1:2929187:C:T"])
variant = conn[0]
print(variant.variant_id, variant.chromosome, variant.annotation.gene_name)
# Every gene-name match, across every page, as a simple iterator:
for variant in client.find_by_gene_all(["BRCA1", "TP53"]):
print(variant.variant_id, [c.name for c in variant.clusters])
# Variants in a region with a population cluster above a MAF threshold:
for variant in client.filter_pos_maf_all("17", start=43044000, end=43125000, maf=0.05, great=True):
print(variant.variant_id)
client.close() # or use `with AGVDClient(...) as client:`
Pagination
Every query that can return many variants (fetch_variant, find_by_gene,
find_by_position, search_variant, filter_pos_maf, filter_ids_maf,
filter_gene_maf) returns one Relay page as a Connection:
conn = client.find_by_gene(["BRCA1"], first=50)
for variant in conn: # this page's variants
...
conn.total_count # matches, ignoring pagination
conn.page_info.has_next_page
next_page = client.find_by_gene(["BRCA1"], first=50, after=conn.page_info.end_cursor)
Each of those methods has an _all counterpart (find_by_gene_all, ...)
that walks every page for you and yields Variant objects directly —
that's what the quickstart above uses. Use the plain method when you only
want one page, or just a count:
conn = client.find_by_gene(["BRCA1"], count_only=True)
conn.total_count # no variant records fetched at all
Variant ID format
The database's internal variantID is stored underscore-joined:
CHROMOSOME_POSITION_REFERENCE_ALTERNATE, e.g. 17_43044295_G_A. Every
id-based lookup does an exact string match against that field — no
normalisation happens server-side, so sending the wrong delimiter silently
matches nothing (no error).
Every method on AGVDClient that takes a variant_id accepts the id in
any common delimiter and normalises it for you — "17:43044295:G:A",
"17-43044295-G-A", and "17_43044295_G_A" are all equivalent. If you're
constructing ids yourself from parts, use agvd.build_variant_id:
from agvd import build_variant_id
vid = build_variant_id("chr17", 43044295, "g", "a") # -> "17_43044295_G_A"
Note on
cli_variant_peek: uniquely among these operations, the server echoesvariant_idback dash-joined in the response (to match the website's URL convention) even though the input it matched against was underscore-joined.AGVDClientdoesn't paper over this — the response model just carries whatever the server actually sent, with a docstring note oncli_variant_peekandVariantAvailability.
API reference
| Method | Returns | Auth | Notes |
|---|---|---|---|
fetch_variant / _all |
Connection[Variant] |
— | by variant_id and/or rs_id |
find_by_gene / _all |
Connection[Variant] |
— | by gene name(s) |
find_by_position / _all |
Connection[Variant] |
— | chromosome, optional exact position or range |
search_variant / _all |
Connection[Variant] |
✓ | free-text, variant id or gene name |
filter_pos_maf / _all |
Connection[Variant] |
— | region + per-cluster MAF threshold |
filter_ids_maf / _all |
Connection[Variant] |
— | id list + per-cluster MAF threshold |
filter_gene_maf / _all |
Connection[Variant] |
— | gene(s) + per-cluster MAF threshold |
cli_variant_search |
list[VariantThresholdResult] |
✓ | ≤500 ids; variantID only — see note below |
cli_vcf_filter |
list[VariantThresholdResult] |
✓ | ≤500 ids; variantID or rsID |
file_upload |
list[VariantThresholdResult] |
✓ | ≤500 ids; threshold accepted but ignored server-side |
cli_variant_peek |
list[VariantAvailability] |
✓ | ≤500 ids; dataset required |
peek |
list[VariantAvailability] |
✓ | convenience: classifies a mixed id list and calls cli_variant_peek for each kind |
fetch_datasets |
list[Dataset] |
— | |
create_dataset |
Dataset |
✓ (admin) | |
site_map_urls |
list[SiteMapURL] |
— |
cli_variant_search only matches on variant_id — despite the
underlying schema input type (VCFQueryInput) nominally accepting an
rsID list too, the server resolver never reads it. Passing only rsIDs
raises AGVDUserInputError before a network call. Use cli_vcf_filter
(which does resolve rsIDs server-side) if you need rsID-based threshold
lookups.
Every *_maf query's great argument means "keep clusters where
maf >= maf" when True, maf <= maf when False/omitted.
dataset on most methods switches on the non-SAALS genotype merge
(AGVDClient.NON_SAALS_DATASET / "AGVD_24A_Main_Non_SAALS") when set to
that value — pass dataset= per call, or set a client-wide default:
AGVDClient(token=..., dataset=agvd.NON_SAALS_DATASET).
Error handling
Every failure raises a subclass of agvd.AGVDError, carrying the
server's (or a client-side) error code as .code:
from agvd import AGVDClient, AGVDUserInputError, AGVDRateLimitError, AGVDError
try:
client.find_by_position("99") # not a real chromosome
except AGVDUserInputError as exc:
print(exc.code, exc) # BAD_USER_INPUT ...
except AGVDRateLimitError as exc:
print("retry after", exc.retry_after, "seconds")
except AGVDError as exc: # catches anything else, including
print(exc.code, exc) # error codes this version doesn't know about yet
| Exception | Code | Cause |
|---|---|---|
AGVDUserInputError |
BAD_USER_INPUT |
Invalid argument (bad MAF/threshold, unknown chromosome, >500 ids, first out of 1–100, ...) |
AGVDAuthenticationError |
UNAUTHENTICATED |
No/invalid/expired token |
AGVDForbiddenError |
FORBIDDEN |
Valid token, wrong role (e.g. non-admin calling create_dataset) |
AGVDQueryTooComplexError |
QUERY_TOO_COMPLEX |
Query exceeded the server's complexity budget |
AGVDRateLimitError |
RATE_LIMITED |
HTTP 429 — 200 requests / 15 min per IP |
AGVDServerError |
INTERNAL_SERVER_ERROR |
5xx or a server-side DB error |
AGVDNetworkError |
NETWORK_ERROR / TIMEOUT |
Never got a response |
5xx responses, network errors, and GraphQL INTERNAL_SERVER_ERROR are
retried automatically with exponential backoff (max_retries=, default
3); 429s wait out Retry-After; everything else (4xx, validation errors)
fails immediately — retrying a deterministic rejection just wastes quota.
The agvd CLI
The CLI (pip install "agvd[cli]") reads a VCF, CSV, TSV, or Excel file of variants, submits them in parallel batches to cli_variant_search, and writes an annotated copy plus a JSON summary. It's a thin wrapper over AGVDClient — see above if you'd rather call the API directly from Python.
agvd --KEY YOUR_TOKEN --INFILE variants.vcf --OUTPUT results.csv --THRESHOLD 0.05
From a CSV/TSV/Excel file with a variant ID column
agvd --KEY YOUR_TOKEN --INFILE variants.csv --COLUMN variant_id --OUTPUT results.csv --THRESHOLD 0.05
From a table with separate chromosome/position columns
agvd --KEY YOUR_TOKEN --INFILE variants.tsv --CHR chrom --POS position --REF ref --ALT alt --OUTPUT results.tsv --THRESHOLD 0.01
Arguments
| Argument | Short | Description |
|---|---|---|
--KEY |
-k |
API token (or set AGVD_TOKEN) |
--INFILE |
-i |
Input file (.vcf, .vcf.gz, .csv, .tsv, .xlsx) |
--OUTPUT |
-o |
Output file path (.csv, .tsv, or .xlsx) |
--THRESHOLD |
-t |
MAF threshold for filtering (e.g. 0.05) |
--DATASET |
-d |
Dataset to query (default: AGVD_24A_Main_Non_SAALS) |
--BATCH |
-b |
Variants per API request, 1–500 (default: 500) |
--COLUMN |
-c |
Column containing variant IDs (CSV/TSV/Excel) |
--CHR / --POS / --REF / --ALT |
Separate chromosome/position/ref/alt column names | |
--base-url |
Override the GraphQL endpoint | |
--threads |
Parallel worker threads (default: 4) |
|
--cache |
Cache API responses in-process (LRU, up to 5000 batches) | |
--dry-run |
Parse and validate ids without making API calls | |
--verbose |
Debug-level logging | |
--peek |
Quick availability check — see below | |
--version |
Print the installed version and exit |
Only variant_id-style rows go to the batch MAF lookup (cliVariantSearch
doesn't support rsID — see the API reference above); rsID rows are marked
UNSUPPORTED in the output rather than silently dropped.
Peek mode
Checks availability (and, for the website, a direct link) for each id, without a threshold comparison. Accepts a mix of rsIDs and variant ids:
agvd --KEY YOUR_TOKEN --peek rs334 17:43044295:G:A
# or from a file, one id per line:
agvd --KEY YOUR_TOKEN --peek --INFILE variants.txt
Output
The output file keeps all original columns and adds:
| Column | Description |
|---|---|
AGVDCUTOFF |
ABOVE, BELOW, NO MATCH, INVALID, UNSUPPORTED (rsID — ...), or ERROR |
African_MAF |
MAF value returned by the API |
THRESHOLD |
Threshold actually applied |
<ClusterName>_MAF |
Per-population cluster MAF (one column per cluster) |
A companion <output>_summary.json is written alongside it:
{ "total": 1000, "successful": 998, "failed": 2, "success_rate": 0.998 }
Development
pip install -e ".[dev]"
pytest
The package lives under src/agvd/ (an installed-package / src layout,
not a flat set of top-level modules) — install it (pip install -e .)
rather than running from the repo root, or rely on pythonpath = ["src"]
in pyproject.toml, which pytest already picks up.
License
MIT License — see LICENSE for details.
Contact
For support or to request an AGVD API token: agvd@afrigen-d.org
Metadata
Release files for agvd 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| agvd-0.1.3.tar.gz | 39.9 kB | Without provenance | Details for agvd-0.1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| agvd-0.1.3-py3-none-any.whl | Python 3 | none | any | Details for agvd-0.1.3-py3-none-any.whl |
Total release size: 70.8 kB
Release files / agvd-0.1.3.tar.gz
| Download URL | agvd-0.1.3.tar.gz |
|---|---|
| Size | 39.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
81269496c3b81e2e6f9acfa6f2dfa1e2b480903ad1b2b17ef773ba348ee40b4f
|
|
BLAKE2b-256 checksum How to use checksums |
e4213bacfe19bca4863b2bfc31c08f9410ea236a37b790b025dae1646129da0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.1
|
Release files / agvd-0.1.3-py3-none-any.whl
| Download URL | agvd-0.1.3-py3-none-any.whl |
|---|---|
| Size | 30.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1518a456233bbd8d34a93637ccbd9994e611653432217e8f023cf5419fc5e5be
|
|
BLAKE2b-256 checksum How to use checksums |
acc58518f7f86f6f570890a7388b45409c7e28c1a0f06923615b1016012dc303
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.1
|
Security for agvd 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.