Skip to content

Concurrent Requests

Asyncio provides multiple ways to run tasks concurrently:

  • asyncio.gather
  • asyncio.TaskGroup (Python 3.11+)

These are convenient ways to get the job done for general use-cases, however, they may not be the best fit for Pulsefire use-cases, as such, a modified TaskGroup is provided out of the box for this purpose.

Example Usage

Request first 20 matches of a LoL summoner.

from pulsefire.clients import RiotAPIClient
from pulsefire.schemas import RiotAPISchema
from pulsefire.taskgroups import TaskGroup
async with RiotAPIClient(default_headers={"X-Riot-Token": <API_KEY>}) as client:
    account = await client.get_account_v1_by_riot_id(region="americas", game_name="200", tag_line="16384")
    summoner = await client.get_lol_summoner_v4_by_puuid(region="na1", puuid=account["puuid"])
    match_ids = await client.get_lol_match_v5_match_ids_by_puuid(region="americas", puuid=summoner["puuid"])

    async with TaskGroup(asyncio.Semaphore(100)) as tg: #(1)!
        for match_id in match_ids[:20]:
            await tg.create_task(client.get_lol_match_v5_match(region="americas", id=match_id)) #(2)!
    matches: list[RiotAPISchema.LolMatchV5Match] = tg.results() #(3)!

    for match in matches:
        assert match["metadata"]["matchId"] in match_ids
  1. Semaphore is optional, provide a semaphore to limit the amount of concurrency.
  2. Unlike asyncio.TaskGroup, the create_task method is async.
  3. Internal collection of task results and exceptions.

Key differences from asyncio.TaskGroup

  • Accepts a semaphore to restrict the amount of concurrent running coroutines.
  • Due to semaphore support, the create_task method is now async.
  • Allows internal collection of results and exceptions, similar to asyncio.Task.
  • If exception collection is on (default), the task group will not abort on task exceptions.
from pulsefire.clients import RiotAPIClient
from pulsefire.schemas import RiotAPISchema
async with RiotAPIClient(default_headers={"X-Riot-Token": <API_KEY>}) as client:
    account = await client.get_account_v1_by_riot_id(region="americas", game_name="200", tag_line="16384")
    summoner = await client.get_lol_summoner_v4_by_puuid(region="na1", puuid=account["puuid"])
    match_ids = await client.get_lol_match_v5_match_ids_by_puuid(region="americas", puuid=summoner["puuid"])

    tasks: list[asyncio.Task] = []
    async with asyncio.TaskGroup() as tg:
        for match_id in match_ids[:20]:
            tasks.append(tg.create_task(client.get_lol_match_v5_match(region="americas", id=match_id)))
    matches: list[RiotAPISchema.LolMatchV5Match] = [task.result() for task in tasks]

    for match in matches:
        assert match["metadata"]["matchId"] in match_ids

About asyncio.TaskGroup

  • The first time any of the tasks belonging to the group fails with an exception other than asyncio.CancelledError, the remaining tasks in the group are cancelled, and exceptions are raised to the scope.
  • Tasks may start to crowd up on memory if task creation is faster than task execution.
from pulsefire.clients import RiotAPIClient
from pulsefire.schemas import RiotAPISchema
async with RiotAPIClient(default_headers={"X-Riot-Token": <API_KEY>}) as client:
    account = await client.get_account_v1_by_riot_id(region="americas", game_name="200", tag_line="16384")
    summoner = await client.get_lol_summoner_v4_by_puuid(region="na1", puuid=account["puuid"])
    match_ids = await client.get_lol_match_v5_match_ids_by_puuid(region="americas", puuid=summoner["puuid"])

    matches: list[RiotAPISchema.LolMatchV5Match] = await asyncio.gather(*[
        client.get_lol_match_v5_match(region="americas", id=match_id)
        for match_id in match_ids[:20]
    ])

    for match in matches:
        assert match["metadata"]["matchId"] in match_ids

About asyncio.gather

  • Requires scheduling all coroutines at once, may cause memory issues if not properly controlled or semaphored.