PubMed Client - Python Bindings¶
Python bindings for the PubMed and PMC (PubMed Central) API client library.
Overview¶
This package provides Python bindings to the Rust-based PubMed client library, enabling high-performance access to PubMed and PMC APIs from Python.
Features¶
PubMed API: Search and retrieve article metadata
PMC API: Access full-text articles from PubMed Central
ELink API: Get related articles, citations, and PMC links
Europe PMC API: Cross-source search (preprints, patents, Agricola, CBA), JATS full text, reference and citation graphs, external database links — no API key needed
SearchQuery Builder: Build complex queries programmatically with filters
High Performance: Built with Rust for speed and reliability
Type-Safe: Full type hints for better IDE support
Installation¶
From PyPI¶
pip install pubmed-client-py
With uv¶
uv add pubmed-client-py
From Source¶
# Clone the repository
git clone https://github.com/illumination-k/pubmed-client.git
cd pubmed-client/pubmed-client-py
# Create virtual environment and install
uv venv
uv run --with maturin maturin develop
Quick Start¶
Basic Usage¶
import pubmed_client
# Create a unified client
client = pubmed_client.Client()
# Search PubMed
articles = client.pubmed.search_and_fetch("covid-19 vaccine", max_results=10)
for article in articles:
print(f"Title: {article.title}")
print(f"PMID: {article.pmid}")
print(f"Journal: {article.journal}")
print()
Fetch Single Article¶
import pubmed_client
client = pubmed_client.Client()
# Fetch article by PMID
article = client.pubmed.fetch_article("31978945")
print(f"Title: {article.title}")
print(f"Authors: {article.author_count}")
print(f"Abstract: {article.abstract_text[:200]}...")
# Access authors and affiliations
for author in article.authors():
print(f" {author.full_name}")
if author.orcid:
print(f" ORCID: {author.orcid}")
Fetch PMC Full-Text¶
import pubmed_client
client = pubmed_client.Client()
# Fetch full text from PMC
full_text = client.pmc.fetch_full_text("PMC7906746")
print(f"Title: {full_text.title}")
print(f"Sections: {len(full_text.sections())}")
print(f"References: {len(full_text.references())}")
# Access article sections
for section in full_text.sections():
print(f"Section: {section.title}")
print(f"Content: {section.content[:200]}...")
# Access figures and tables
for figure in full_text.figures():
print(f"Figure: {figure.label}")
print(f"Caption: {figure.caption}")
# Convert to Markdown
markdown = full_text.to_markdown()
print(markdown)
Citation Analysis¶
import pubmed_client
client = pubmed_client.Client()
# Get citations for an article
citations = client.get_citations([31978945])
print(f"Citation count: {len(citations)}")
# Get citing article PMIDs
for citing_pmid in citations.citing_pmids[:10]:
print(f"Cited by: {citing_pmid}")
Using SearchQuery Builder¶
import pubmed_client
client = pubmed_client.Client()
# Build a complex query
query = (
pubmed_client.SearchQuery()
.query("cancer")
.published_between(2020, 2024)
.article_type("Clinical Trial")
.free_full_text_only()
.limit(50)
)
# Execute the search
articles = client.pubmed.search_and_fetch(query, 0) # limit ignored when using SearchQuery
for article in articles:
print(f"[{article.pmid}] {article.title}")
Field, MeSH, and Validation Filters¶
import pubmed_client
query = (
pubmed_client.SearchQuery()
.title_or_abstract("gene therapy")
.author("Doudna JA")
.journal("Nature")
.mesh_term("Neoplasms")
.mesh_major_topic("Genetic Therapy")
.affiliation("Harvard")
.language("english")
.human_studies_only()
.has_abstract()
)
# Raises InvalidQueryException if the query is malformed
query.validate()
# Drop duplicate terms/filters, then inspect the query
query.optimize()
terms, filters, complexity = query.get_stats()
print(query.build())
Also available: first_author(), last_author(), journal_abbreviation(),
title_contains(), abstract_contains(), grant_number(), isbn(), issn(),
mesh_terms(), mesh_subheading(), orcid(), organism_mesh(),
animal_studies_only(), age_group(), and custom_filter() for raw PubMed syntax.
Boolean Query Combinations¶
import pubmed_client
# Build complex queries with boolean logic
q1 = pubmed_client.SearchQuery().query("covid-19")
q2 = pubmed_client.SearchQuery().query("vaccine")
q3 = pubmed_client.SearchQuery().query("efficacy")
# Combine with AND
combined = q1.and_(q2).and_(q3)
print(combined.build()) # ((covid-19) AND (vaccine)) AND (efficacy)
# Combine with OR
either = q1.or_(q2)
print(either.build()) # (covid-19) OR (vaccine)
# Exclude specific terms
base = pubmed_client.SearchQuery().query("treatment")
excluded = pubmed_client.SearchQuery().query("animal studies")
human_only = base.exclude(excluded)
print(human_only.build()) # (treatment) NOT (animal studies)
Extract Figures from PMC Articles¶
import pubmed_client
client = pubmed_client.Client()
# Download and extract figures with captions
figures = client.pmc.extract_figures_with_captions("PMC7906746", "./output")
for fig in figures:
print(f"Figure: {fig.figure.label}")
print(f"Caption: {fig.figure.caption}")
print(f"File: {fig.extracted_file_path}")
print(f"Size: {fig.file_size} bytes")
if fig.dimensions:
print(f"Dimensions: {fig.dimensions[0]}x{fig.dimensions[1]}")
Europe PMC¶
Europe PMC complements the NCBI E-utilities: it indexes preprints (PPR),
patents (PAT), Agricola (AGR) and Chinese Biological Abstracts (CBA) alongside PubMed (MED)
and PMC, and needs no API key.
Records are addressed by a source database plus an id. Every method accepts the id bare
("PMC3258128", "33515491"), with an explicit source, or fully qualified
("PPR/PPR123456"). Given no source, a PMC-prefixed id is read as a PMC record and anything else
as a PubMed record.
import pubmed_client
client = pubmed_client.Client()
# Cross-source search, including preprints
for result in client.europe_pmc.search("TITLE:CRISPR AND SRC:PPR", 5):
print(result.europe_pmc_id, result.title)
# Full text as a parsed article, or as raw JATS XML for non-PMC sources
article = client.europe_pmc.fetch_full_text("PMC3258128")
xml = client.europe_pmc.fetch_full_text_xml("PMC3258128")
# Citation graph in both directions
references = client.europe_pmc.get_references("PMC3258128")
citations = client.europe_pmc.get_citations("33515491", source="MED")
# Cross-references to external databases (UniProt, EMBL, PDB, ...)
for link in client.europe_pmc.get_database_links("PMC3258128"):
print(link.db_name, link.db_count)
# `resultType="core"` returns far more than is modelled; the remainder is
# available as a plain dict rather than pinned to a schema Europe PMC may change
for result in client.europe_pmc.search_all("malaria vaccine", 10, result_type="core"):
print(result.extra().get("citedByCount"))
Paging is explicit where Europe PMC exposes it — search_page returns a next_cursor_mark, and
get_references_page / get_citations_page return (hit_count, entries).
Configuration¶
With API Key¶
Using an NCBI API key increases rate limits from 3 to 10 requests per second:
import pubmed_client
config = (
pubmed_client.ClientConfig()
.with_api_key("your_ncbi_api_key")
.with_email("your@email.com")
.with_tool("YourAppName")
)
client = pubmed_client.Client.with_config(config)
Rate Limiting¶
import pubmed_client
config = (
pubmed_client.ClientConfig()
.with_rate_limit(2.0) # 2 requests per second
.with_timeout_seconds(60) # 60 second timeout
)
client = pubmed_client.Client.with_config(config)
Type Hints and IDE Support¶
The package includes complete type stubs (.pyi files) for full IDE autocomplete and type checking:
import pubmed_client
# Type hints work automatically
config: pubmed_client.ClientConfig = pubmed_client.ClientConfig()
client: pubmed_client.Client = pubmed_client.Client.with_config(config)
articles: list[pubmed_client.PubMedArticle] = client.pubmed.search_and_fetch("query", 10)
Run type checking with mypy:
mypy your_script.py
Maintaining the type stub¶
pubmed_client.pyi is generated, not hand-edited. It is produced from the
#[gen_stub_pyclass] / #[gen_stub_pymethods] annotations in the Rust source by
the stub_gen binary, which then splices in the members pyo3-stub-gen cannot
introspect (__version__ and the create_exception! hierarchy).
When you add or change a PyO3 class, method, or attribute:
Annotate the new
#[pyclass]with#[gen_stub_pyclass]and every#[pymethods]block with#[gen_stub_pymethods](module-level items added viam.add(...), such as new exceptions, also need to be listed instub_gen.rs).Regenerate the stub:
MISE_ENV=python mise run stubgen:py # or: cargo run --bin stub_gen
Verify it matches the compiled module:
MISE_ENV=python mise run stubtest:py # maturin develop + python -m mypy.stubtest
Commit the regenerated
pubmed_client.pyi.
CI enforces both halves: the Python Type Stub Check job regenerates the stub and
fails on git diff if the checked-in copy is stale, then runs stubtest so the
stub can never silently drift from the compiled module. stubtest-allowlist.txt
records the intentionally-unstubbed internal extension submodule.
Development¶
Prerequisites¶
Python >= 3.12
Rust toolchain (installed via rustup)
uv (for Python package management)
maturin (for building Python bindings)
Setup Development Environment¶
# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment
cd pubmed-client-py
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Build and install in development mode
uv run --with maturin maturin develop
Running Tests¶
# Install dev dependencies
uv sync --group dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=pubmed_client
Code Quality¶
# Format code
uv run ruff format
# Lint code
uv run ruff check
# Type checking
uv run mypy tests/
Building¶
# Build wheel
uv run --with maturin maturin build --release
# Build for distribution
uv run --with maturin maturin build --release --sdist
Publishing¶
# Publish to PyPI (requires credentials)
uv run --with maturin maturin publish
License¶
MIT