Skip to content

Async

Every entry point has an async twin using real async I/O over httpx, not a thread-pool wrapper.

sync async
fetch() afetch()
iter_reviews() aiter_reviews()
iter_pages() aiter_pages()
fetch_page() afetch_page()
search() asearch()
lookup() alookup()
version_history() aversion_history()
reply() / get_reply() areply() / aget_reply()
delete_reply() (App Store) adelete_reply()
versions() aversions()
close() / with aclose() / async with
import asyncio

from app_reviews import AppStoreReviews, Country


async def main():
    async with AppStoreReviews() as client:
        result = await client.afetch(
            "324684580", countries=[Country.US, Country.GB]
        )
    for outcome in result.outcomes:
        print(outcome.country, outcome.reviews_fetched, outcome.stopped_because)


asyncio.run(main())

afetch() fans out across countries with asyncio.gather behind a semaphore, with no threads. concurrency=1 makes it sequential.

async def walk_pages(client):
    async for page in client.aiter_pages("324684580", country="us"):
        await store(page.reviews)
async def stream_reviews(client):
    async for review in client.aiter_reviews("324684580", countries=["us", "gb"]):
        await handle(review)

The sync path is a separate implementation, not a wrapper around the async one, so calling fetch() from inside a running event loop is safe.

Async credential work stays off the event loop: key loading and App Store JWT signing, which are blocking local work with no async equivalent, run in a thread, and the Google Play token exchange is awaited. Either way each client caches its token and reuses it until it nears expiry, then signs or exchanges a fresh one; it does not authenticate once per request.


Closing the pool

Each client owns one connection pool, so its sockets outlive a single request. Use async with, or call aclose():

async def main():
    async with AppStoreReviews() as client:
        result = await client.afetch("324684580")

Async search and lookup

AppStoreSearch and GooglePlaySearch follow the same pattern:

from app_reviews import AppStoreSearch


async def main():
    async with AppStoreSearch() as client:
        results = await client.asearch("fitness tracker")
        app = await client.alookup("com.burbn.instagram")

Async metadata lookup

from app_reviews import AppStoreSearch


async def main():
    async with AppStoreSearch() as client:
        metadata = await client.alookup("123456789")