Usage in an asynchronous environment¶
bloonspy offers the asynchronous bloonspy.AsyncClient API which you can use in asynchronous environments.
Returned objects and models are exactly the same as its synchronous counterpart, with a few important differences:
bloonspy.AsyncClienthas to be instanced, none of its methods are static.Function calls have to be awaited
Trying to access a property that hasn’t been loaded of any model will raise
bloonspy.exceptions.NotLoaded
from bloonspy import AsyncClient
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
client = AsyncClient(aiohttp_client=session)
await get_race_summary(client)
await get_race_summary_wrong(client)
async def get_race_summary(client: AsyncClient):
"""Print general information about the latest race events."""
races = await client.races()
# Load all Race objects concurrently
await asyncio.gather(*[race.load_resource() for race in races])
for race in races:
print(f"Race {race.name} went from {race.start} to {race.end}. "
f"It was on {race.gamemode.difficulty.value} difficulty and "
f"ended at round {race.end_round}.")
async def get_race_summary(client: AsyncClient):
"""This is how you DON'T do it."""
races = await client.races()
for race in races:
# Raises bloonspy.exceptions.NotLoaded, because you tried to access
# Race.start on a Race object which wasn't loaded. The synchronous client
# would have fetched it for you under the hood, but the async one doesnt.
# You should load the resource first and then access what you need.
print(f"Race {race.name} started at round {race.start}.")
if __name__ == "__main__":
asyncio.run(main())
Since all objects are lazy loaded by default, it’s important to call the bloonspy.model.Loadable.load_resource()
or bloonspy.model.Event.load_event() explicitely at times, or set the eager parameter to True on methods
that support it.
Note that not all properties are lazily loaded. Some can be loaded right away, in the example below, Race.name is
already loaded when a result of bloonspy.Client.races(), so no need to load the whole resource.
This is lined out in the documentation for the function.
async def print_race_difficulty_bad(client: AsyncClient):
race = await client.races()[0]
# Bad! Since Race.gamemode is not currently loaded,
# bloonspy.exceptions.NotLoaded will be raised.
print(race.gamemode.difficulty.value)
async def print_race_difficulty_good(client: AsyncClient):
race = await client.races()[0]
# Good! Load the resource and await it before accessing lazy loaded properties.
await race.load_resource()
print(race.gamemode.difficulty.value)
race = await client.races(eager=True)[1]
# Also good! The races were loaded thanks to eager=True.
print(race.gamemode.difficulty.value)
async def print_race_name(client: AsyncClient):
race = await client.races()[0]
# As pointed out in the API reference page, Race.name already gets
# loaded when calling Client.races, so there's no need to load the whole
# resource if not necessary.
print(race.name)
For more info on lazy and eager loading, see the section below.
Lazy and Eager loading¶
By default, whenever you request a resource, it will be lazily loaded, meaning not all properties will be available straight away. They will be loaded when you need them instead; this is to limit unnecessary API calls that could make you hit the rate limit. For example:
>>> # The race event is fetched, but only has a few properties available.
>>> # Read the documentation on the Client.get_race function to see which ones.
>>> race = Client.get_race("The_Abyssal_Plane_lfbrs5fb")
>>> # All methods are still available and don't take extra API calls
>>> # to function, unless explicitely specified in the docs.
>>> players = race.leaderboard()
>>> # The gamemode property is not loaded, so Race.load_resource is called to load it.
>>> print(race.gamemode)
In this example, when we lazy load race, only the properties name, start, end, and total_scores
will be loaded right away. If we want one that isn’t loaded (in this example, gamemode), it will be fetched
automatically via an API call.
If you want race to be fully loaded right away, you can always set the eager parameter to True.
>>> # This will load the race and automatically call Race.load_resource and return a fully loaded object.
>>> race = Client.get_race("The_Abyssal_Plane_lfbrs5fb", eager=True)
>>> # No extra API calls are made since the property is already loaded.
>>> print(race.gamemode)