Skip to content

Querying

Every entity resource exposes the same query() signature:

client.vn.query(
    filters=None,   # Predicate, raw list, or None
    fields=None,    # comma-separated field string; defaults to the model's fields
    sort=None,      # field name to sort by
    reverse=None,   # reverse the sort order
    results=None,   # page size
    page=None,      # 1-based page number
    count=None,     # ask the API for the total count
    user=None,      # user id, for user-scoped endpoints like ulist
)

Fetch one entity by id

A by-id lookup is just a query() with an id equality filter. Every entity id is a string with its type prefix (v for VNs, r for releases, c for characters, …), e.g. "v17":

from vndb_client import Client

with Client() as client:
    page = client.vn.query(filters=["id", "=", "v17"])
    vn = page.results[0]
    print(vn.title)        # 'Ever17 -the out of infinity-'
    print(vn.rating)       # 84.3
    print(vn.released)     # '2002-08-29'
    print(vn.languages)    # ['en', 'ja', 'zh-Hans', ...]

What you get back

query() always returns a Page[VN] envelope — even for a single id. Its fields are:

Field Type Meaning
results list[VN] The matched models. Empty if the id does not exist.
more bool Whether further pages exist (always False by id).
count int \| None Total match count; populated only when count=True.
compact_filters str \| None Echoed compact filter string when requested.
normalized_filters list \| None Echoed normalised filter list when requested.

An unknown id is not an error — it returns an empty results list, so guard with page.results[0] if page.results else None.

Each VN in results is a typed Pydantic model. The fields requested by default include:

Field Type Notes
id str Always present, e.g. "v17".
title str \| None Main title in its original language.
alttitle str \| None Alternative (romanised) title.
titles list[Title] \| None Per-language titles.
aliases list[str] \| None Known aliases.
released str \| None Release date YYYY-MM-DD.
languages / platforms list[str] \| None Language and platform codes.
rating / average float \| None Bayesian rating / raw average (10–100).
votecount int \| None Number of votes.
length / length_minutes int \| None Length bucket / play time in minutes.
description str \| None Description (may contain BBCode).
image Image \| None Cover image metadata.

Pass fields to fetch more (e.g. nested relations); see below. Unknown keys in the response are ignored, so a narrower or wider fields selection never raises.

Fields

By default the client requests the fields its model declares. Pass fields to narrow or extend the selection:

from vndb_client import Client

with Client() as client:
    page = client.vn.query(filters=["search", "=", "muv-luv"], fields="id,title,rating")
    print(page.results[0].title)

Pagination

Results are paged. Use results for page size and page for the page number, and check page.more to decide whether to continue:

from vndb_client import Client

with Client() as client:
    page_no = 1
    while True:
        page = client.vn.query(filters=["search", "=", "fate"], results=25, page=page_no)
        for vn in page.results:
            print(vn.id, vn.title)
        if not page.more:
            break
        page_no += 1

Counting

Pass count=True to populate page.count with the total number of matches:

from vndb_client import Client

with Client() as client:
    page = client.vn.query(filters=["search", "=", "fate"], results=1, count=True)
    print(page.count)

Sorting

from vndb_client import Client

with Client() as client:
    page = client.vn.query(filters=["search", "=", "key"], sort="rating", reverse=True)
    print([vn.title for vn in page.results])

Response caching

Reads are not cached by default. Pass cache_ttl (seconds) to enable an in-memory cache of read responses on a client; identical reads within the TTL are served without a network call:

from vndb_client import Client

with Client(cache_ttl=60.0) as client:
    client.vn.query(filters=["search", "=", "ever17"])  # network
    client.vn.query(filters=["search", "=", "ever17"])  # served from cache

The cache is per-client (not shared across clients or tokens), bounded by cache_maxsize (default 128, least-recently-used eviction), and applies only to reads — writes (set_ulist/delete_ulist/set_rlist/delete_rlist) always hit the API. Staleness is bounded by cache_ttl.