Skip to main content

Python interface for the OpenAIRE Graph API, built on top of the bibliofabric framework.

Project description

AIREloom: Asynchronous Python client for the OpenAIRE API

AIREloom logo

Samuel Mok · s.mok@utwente.nl · 2025–2026

AIREloom is an async Python client for the OpenAIRE Graph API and Scholexplorer API, built on bibliofabric.

Docs: utsmok.github.io/AIREloom · PyPI: aireloom · License: MIT

Features

  • Full API coverage — Research Products (v2), Projects, Organizations, Data Sources, Persons, Research Product Links (v1), Scholexplorer (v3)
  • Async by design — built on httpx + asyncio with proper connection pooling
  • Typed throughout — Pydantic models for all inputs/outputs, PEP 561 py.typed marker
  • Ergonomics layer — computed properties, SafeStr/SafeList defaults, convenience queries, iterator helpers
  • Flexible auth — auto-detection from env vars, static tokens, OAuth2 client credentials, or no auth
  • Resilient — retries with backoff, rate-limit handling (Retry-After), optional client-side caching, hook system

Installation

uv add aireloom

Or with pip: pip install aireloom. Requires Python ≥3.12.

Quick Start

import asyncio
from aireloom import AireloomSession
from aireloom.endpoints import ResearchProductsFilters

async def main():
    async with AireloomSession() as session:
        # Search publications
        filters = ResearchProductsFilters(
            type="article", mainTitle="climate change",
            fromPublicationDate="2024-01-01",
        )
        response = await session.research_products.search(
            filters=filters, page_size=5, sortBy="publicationDate desc",
        )
        for product in response.results:
            print(f"{product.title} — DOI: {product.doi or 'N/A'}")

        # Iterate all results (cursor-based auto-pagination)
        filters2 = ResearchProductsFilters(type="dataset", countryCode="NL")
        async for product in session.research_products.iterate(
            filters=filters2, page_size=50,
        ):
            print(product.title)
            break  # stop when you want

        # Get a single entity
        product = await session.research_products.get("doi:10.1038/s41586-021-03964-9")
        print(product.title, product.doi)

        # Convenience queries
        citations = await session.queries.citing_works("doi:10.1038/s41586-021-03964-9")
        print(f"{len(citations)} citations")

asyncio.run(main())

No authentication required — the OpenAIRE API works without it. For higher rate limits, see Authentication.

Core API

Retrieval methods

Every resource client (session.research_products, session.organizations, etc.) provides:

Method Description
get(id) Retrieve a single entity by ID
search(filters, page, page_size, sortBy) Paginated search
iterate(filters, page_size, sortBy) Auto-paginate all results (cursor-based)
collect(limit, ...) Collect results into a list
count(filters) Count matching entities
first(filters) Get first matching entity or None

Resource clients

Client API Notes
session.research_products Graph API v2 Enriched responses with related entities
session.projects Graph API v1
session.organizations Graph API v1
session.data_sources Graph API v1
session.persons Graph API v1 givenName/lastName filters cause 500s (upstream bug)
session.scholix Scholix v3 search_links() / iterate_links()

Ergonomics

Computed properties on models — derived fields that don't exist in the raw API response:

product.doi              # → "10.1234/example" (extracted from pids)
product.publication_year  # → 2024 (from publicationDate)
product.is_open_access   # → True (from bestAccessRight)
org.ror_id               # → "https://ror.org/..." (from rorId)
project.funder_name      # → "EC" (from funding array)

SafeStr/SafeListNone is never returned for string/list fields; you get "" or [] instead:

product.subjects  # [] instead of None
product.title     # "" instead of None

Convenience queries via session.queries:

session.queries.publications_by_doi("10.1234/example")
session.queries.citing_works("doi:10.1234/example")
session.queries.datasets_by_organization("openaire____::orgID:grid.5522.e")
session.queries.projects_by_funder("EC")

See Ergonomics docs for the full list.

Filters & Sorting

Filters are Pydantic models — import from aireloom.endpoints:

from aireloom.endpoints import ResearchProductsFilters, ProjectsFilters

filters = ResearchProductsFilters(
    type="article",
    mainTitle="machine learning",
    fromPublicationDate="2024-01-01",
    countryCode="NL",
)

Sort with sortBy="field asc" or sortBy="field desc". Valid fields depend on the endpoint (see ENDPOINT_DEFINITIONS).

Authentication

AIREloom auto-detects auth from environment variables or .env files (prefixed with AIRELOOM_). No auth is the default if nothing is configured.

# Option 1: Static token
AIRELOOM_OPENAIRE_API_TOKEN=your_token

# Option 2: OAuth2 client credentials
AIRELOOM_OPENAIRE_CLIENT_ID=your_id
AIRELOOM_OPENAIRE_CLIENT_SECRET=your_secret

Or pass explicitly:

from bibliofabric.auth import NoAuth, StaticTokenAuth, ClientCredentialsAuth

session = AireloomSession(auth_strategy=StaticTokenAuth(token="..."))

See Authentication docs for details.

Error Handling

from bibliofabric.exceptions import (
    BibliofabricError, APIError, NotFoundError, RateLimitError,
    TimeoutError, NetworkError, AuthError, ValidationError,
)

See Error Handling docs for the full hierarchy.

Examples

All examples in examples/ are dual-purpose — run as scripts or as interactive marimo notebooks:

# As a script
uv run examples/simple_example.py

# As an interactive notebook
uv run marimo edit examples/simple_example.py
Script Description
simple_example.py Search, iterate, get research products
02_scholix_link_discovery.py Discover publication–dataset links via Scholexplorer
03_research_product_analysis.py Deep-dive into product metadata
04_organization_projects.py Organizations and their projects
05_advanced_filtering.py Complex filter combinations
06_persons_discovery.py Person search and co-authorship
07_ergonomics_showcase.py Before/after: raw API vs ergonomics layer
08_iterator_helpers.py collect(), count(), first()
09_computed_fields_and_safe_types.py Computed properties and SafeStr/SafeList
10_convenience_queries.py All convenience query functions

See examples/README.md for marimo embedding and WASM details.

Known OpenAIRE API Issues

Full bug report with reproduction steps: OPENAIRE_BUG_REPORT.md.

  • Persons givenName/lastName filters — cause HTTP 500 despite being in the spec
  • pageSize=100 on Links/Scholix — silently falls back to 10. Use pageSize ≤ 99
  • Undocumented endpoints — Persons has no filtering docs; Links (/v1/researchProducts/links) is entirely undocumented
  • Sort field issues — Persons only accepts relevance; Data Sources error messages say "organizations"

Development

uv sync                  # install dev deps
uv run pytest -x -q      # run tests
uvx ruff check src/      # lint
uvx ty check src/        # type check

Pre-commit hooks for ruff format + lint are configured in .pre-commit-config.yaml.

Contributions welcome — see Contributing.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aireloom-0.5.0a1.tar.gz (334.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aireloom-0.5.0a1-py3-none-any.whl (56.4 kB view details)

Uploaded Python 3

File details

Details for the file aireloom-0.5.0a1.tar.gz.

File metadata

  • Download URL: aireloom-0.5.0a1.tar.gz
  • Upload date:
  • Size: 334.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for aireloom-0.5.0a1.tar.gz
Algorithm Hash digest
SHA256 4f7bb8cf69ceb39fdfd3aa7a06d3a049232db7db6ca22d639c384cb72f1fffc7
MD5 343121cb81acd0eeea2cad0571c5fcc3
BLAKE2b-256 d05e7eb4539c4a3e632654836ebf66a784df1f86ea5c6eff23f85c5202e67f30

See more details on using hashes here.

File details

Details for the file aireloom-0.5.0a1-py3-none-any.whl.

File metadata

  • Download URL: aireloom-0.5.0a1-py3-none-any.whl
  • Upload date:
  • Size: 56.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for aireloom-0.5.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 c3bf5a8cb36d719da3102ccdec8ee7b9443e4efe8bb9ead7fbcf45b8ccf7a156
MD5 43d5220fc0d478d259f8b451b826ece2
BLAKE2b-256 10f2dd6ba0e07c9016d91c51270203077f15f85a5f359ead280e32d32eca4521

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page