Concurrency and Performance

The YouVersion Bible Client supports concurrent operations through the AsyncClient, enabling efficient parallel API calls.

Why Use Concurrency?

  • Faster Execution: Multiple requests happen simultaneously

  • Better Resource Usage: Efficient use of network and CPU

  • Improved User Experience: Reduced waiting time

Basic Concurrency

Using asyncio.gather()

Execute multiple API calls concurrently:

import asyncio
from youversion.clients import AsyncClient

async def get_all_data():
    async with AsyncClient() as client:
        # All three calls happen concurrently
        votd, highlights, notes = await asyncio.gather(
            client.verse_of_the_day(),
            client.highlights(page=1),
            client.notes(page=1)
        )
        return votd, highlights, notes

results = asyncio.run(get_all_data())

Concurrent Pagination

Fetch multiple pages concurrently:

import asyncio
from youversion.clients import AsyncClient

async def get_multiple_pages(max_pages=5):
    async with AsyncClient() as client:
        # Fetch pages 1-5 concurrently
        tasks = [
            client.highlights(page=i)
            for i in range(1, max_pages + 1)
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Flatten results, handling errors
        all_highlights = []
        for result in results:
            if isinstance(result, list):
                all_highlights.extend(result)
            elif isinstance(result, Exception):
                print(f"Error: {result}")

        return all_highlights

highlights = asyncio.run(get_multiple_pages())

Error Handling in Concurrent Operations

Handle errors in concurrent operations:

import asyncio
import httpx
from youversion.clients import AsyncClient

async def safe_concurrent_calls():
    async with AsyncClient() as client:
        results = await asyncio.gather(
            client.verse_of_the_day(),
            client.highlights(page=1),
            client.notes(page=1),
            return_exceptions=True  # Don't fail all if one fails
        )

        votd, highlights, notes = results

        # Check each result
        if isinstance(votd, Exception):
            print(f"VOTD error: {votd}")
            votd = None

        if isinstance(highlights, Exception):
            print(f"Highlights error: {highlights}")
            highlights = []

        if isinstance(notes, Exception):
            print(f"Notes error: {notes}")
            notes = []

        return votd, highlights, notes

results = asyncio.run(safe_concurrent_calls())

Rate Limiting

Respect API Rate Limits

Implement rate limiting for concurrent requests:

import asyncio
from asyncio import Semaphore
from youversion.clients import AsyncClient

async def rate_limited_requests(semaphore, requests):
    async with semaphore:
        async with AsyncClient() as client:
            return await client.verse_of_the_day()

async def batch_with_rate_limit(max_concurrent=5):
    semaphore = Semaphore(max_concurrent)
    tasks = [
        rate_limited_requests(semaphore, i)
        for i in range(20)  # 20 requests, max 5 concurrent
    ]
    results = await asyncio.gather(*tasks)
    return results

results = asyncio.run(batch_with_rate_limit())

Batching Operations

Process data in batches:

import asyncio
from youversion.clients import AsyncClient

async def process_in_batches(items, batch_size=10):
    async with AsyncClient() as client:
        for i in range(0, len(items), batch_size):
            batch = items[i:i + batch_size]
            # Process batch concurrently
            tasks = [process_item(item) for item in batch]
            await asyncio.gather(*tasks)

async def process_item(item):
    # Process individual item
    pass

Performance Tips

  1. Reuse Client: Create one client and reuse it

  2. Batch Requests: Group related requests together

  3. Use Semaphores: Limit concurrent requests

  4. Handle Errors: Use return_exceptions in gather()

  5. Cache Results: Cache expensive operations

Example: Complete Concurrent Workflow

import asyncio
from youversion.clients import AsyncClient
from asyncio import Semaphore

async def comprehensive_data_fetch():
    semaphore = Semaphore(5)  # Max 5 concurrent requests

    async def fetch_with_limit(coro):
        async with semaphore:
            return await coro

    async with AsyncClient() as client:
        # Fetch all data concurrently with rate limiting
        results = await asyncio.gather(
            fetch_with_limit(client.verse_of_the_day()),
            fetch_with_limit(client.highlights(page=1)),
            fetch_with_limit(client.notes(page=1)),
            fetch_with_limit(client.bookmarks(page=1)),
            fetch_with_limit(client.get_bible_versions("eng", "all")),
            return_exceptions=True
        )

        # Process results
        data = {}
        keys = ['votd', 'highlights', 'notes', 'bookmarks', 'versions']

        for key, result in zip(keys, results):
            if isinstance(result, Exception):
                print(f"Error fetching {key}: {result}")
                data[key] = None
            else:
                data[key] = result

        return data

data = asyncio.run(comprehensive_data_fetch())