Cover image for Using a Dataclass for Shared Asyncio State

Using a Dataclass for Shared Asyncio State

Fri Nov 25 2022

pythonasynciodataclass

When I initially started the hypecycle backend project, I needed a way to keep a shared state between all the asyncio tasks that were running. The primary desire for shared state was so that I could easily push all of the output of those tasks onto a websocket.

The easiest way I found to do this was to create a shared object in the global namespace of main.py using the type() method. This works great, but as I have defined a number of fields and their defaults, it has gotten messy and shouldn't be floating around in our entry point script(see below).

# Shared State
hypecycleState = type('SharedData', (), {})()
hypecycleState.gps_active = True
hypecycleState.hr_available = False
hypecycleState.power_available = False
hypecycleState.ride_paused = False
hypecycleState.is_active = False
hypecycleState.battery_level = 100.0
hypecycleState.fix_quality = 0
hypecycleState.instantaneous_power = 0
hypecycleState.power3s = 0
hypecycleState.power10s = 0
hypecycleState.cadence = 0
hypecycleState.bpm = 0
hypecycleState.speed = 0.0
hypecycleState.max_speed = 0.0
hypecycleState.gps_altitude = 0.0
hypecycleState.altitude = 0.0
hypecycleState.temperature = 0.0
hypecycleState.location = {
    "latitude": 0.0,
    "longitude": 0.0,
}
...

Luckily the youtube algorithm, in its infinite wisdom, suggested a video by ArjanCodes that explains the use of python Data Classes and it looked like a nice way to clean up my current mess. So I gave it a try.

The beauty of data classes is that they have a bunch of nifty functionality built in. For instance, one can easily define the type and the default. Below is an example of our new data class for hypecycle state.

from dataclasses import dataclass

@dataclass
class State:
"""Class for keeping track of hypecycle state."""
    gps_active: bool = True
    fix_quality: int = 0
    battery_level: float = 100.0

With this we can create a new state instance with hypecycleState = State() and it will automatically create our state instance with all our defaults that we defined in the class member variables. We can then just use this as a drop in replacement for our previous hypecycleState object.

There is however one hiccup! Our location variable if an object containing latitude and longitude values, but when we try define this as:

    location: dict = {
        'latitude': 0.0,
        'longitude'
    }

The python interpreter freaks out with a ValueError: mutable default <class 'location'> for field a is not allowed: use default_factory . Luckily for more complex mutable data types like our location dictionary the dataclass package has the notion of a field that you can define a default_factory to create your defaults. So with this we define location as follows, and everything works:

location: dict[str, float] = field(
    default_factory=lambda: {'X-latitude':0.0,'longitude': 0.0}
)

So with that we now have carved our state nicely into it's own little dataclass and it makes the code much neater and easier to reason about :D