stravalib icon indicating copy to clipboard operation
stravalib copied to clipboard

Calories, kilojoules and other metrics are None

Open samirak93 opened this issue 4 years ago • 12 comments

Hi, On the official API page, the sample get_activities seems to have numeric values for calories, kilojoules and other metrics. https://developers.strava.com/docs/reference/#api-Activities-getActivityById

get_activities returns None for these metrics. Is this expected? Any reason behind this?

samirak93 avatar Jun 23 '20 04:06 samirak93

hey @samirak93 i'm going through old issues to see what has been fixed and what we need to work on. Have you tried this recently with the big overhaul we've done to the stravalib api? Please let me know. we can leave this open if it's still an issue or close if it's not. i definitely see what you are seeing with the example API response. but am wondering if this has been fixed with the most recently updates that we've made.

lwasser avatar Jan 17 '24 03:01 lwasser

i actually see another recent issue here related to this from @martin-ueding which suggests this may still be a bug. confirmation would be great.

lwasser avatar Jan 17 '24 03:01 lwasser

I use stravalib 1.5 in my project and it downloads 0 for the calories. I've just checked with it.

martin-ueding avatar Jan 18 '24 16:01 martin-ueding

Hi, I'm getting sensible values in responses. Can you make sure that the raw http response has a value for calories for your specific request?

jsamoocha avatar Jan 18 '24 16:01 jsamoocha

hey @jsamoocha 👋 SO -- i just finally had time to test this locally. i went on a hike this morning before work and i have the hike here in strava and see my calories in strava. however locally when i pull the hike down i see this: calories: None when i print out the dictionary return for the data. i will try to back track through my code and add breakpoints to try to see what the API response looks like.

however, is there a simpler way to view the raw http response other than breakpoints in the code for when that response is ingested? i'm going to try manually now but i just wondered if there was an easier way to test this. many thanks! normally with me this is user error 😆

lwasser avatar Jan 20 '24 02:01 lwasser

Ok update here is what i did.

i authenticated & got an activity ID for today's hike. then i made a direct call using requests.

activities = client.get_activities(after="2024-01-18")
api_url = f"https://www.strava.com/api/v3/activities/{activity_id}"
headers = {"Authorization": f"Bearer {my_token}"}
response = requests.get(api_url, headers=headers)
a = response.json()
a["calories"]

that returned 488 which is what i expected (and why i'm eating salted dark chocolate covered caramels now).

However this - returns None for calories

# Assume i've already authenticated and such - i have a little helper to do that
activities = client.get_activities(after="2024-01-18")

for i, activity in enumerate(activities):
    print(i)
    strava_data = activity.to_dict()

print(strava_data["calories"])

the above returns None making me think i should slow down on the caramels. Something does seem up with this but i can't yet figure out how to track down where the issue is - especially if you are seeing ok values.

lwasser avatar Jan 20 '24 02:01 lwasser

Ah, I finally see the issue. get_activities() (plural) returns (below the surface, on the Strava API level) a list of SummaryActivity objects, which don't have calories as attribute.

I tested the above using get_activity() (singular), which returns a DetailedActivity.

To retrieve the activity's calories, one needs to make a request per activity using get_activity().

To prevent confusion in the future, we could also differentiate between the different levels of detail. But that will introduce a breaking change.

jsamoocha avatar Jan 20 '24 06:01 jsamoocha

ahhhhh i should have looked more closely. this works:

activities = client.get_activities(after="2024-01-18")
for activity in activities:
    fin = client.get_activity(activity.id)
    fin.calories

i saw where calories was stored in the code but i made the (wrong) assumption that that would be inherited into the return for get_Activities (there is no factual basis for that assumption).

i really spun on this one as i thought perhaps it was something that was happening when the data were ingested from the api call into the model so i kept trying to create the right breakpoints vs actually just looking at where and how calories were stored 🙈 .

I'm thinking we (as a fix for now)

  1. update docs (i've been meaning to do this i can work on a docstring and documentation update
  2. provide a small usage example in the object docstring to direct people.

That would not break anything but it might help users. thoughts?

lwasser avatar Jan 20 '24 15:01 lwasser

ok thinking about this a bit more -

i understand now what's happening. i do see what the API returns for that call by default and it doesn't include any of that "detailed level" data such as calories

but the Activity class that is returned via the batch iterator object inherits from detailedActivity so it by default has all of the detail level attrs in it (but they are empty). As a user that is confusing because you see the dictionary element in the object return calories if you use .keys() so one would expect the value to be there.

i don't recall how it behaved previously. what would be the breakpoint if we were to address this and make sure only the items returned by the strava API were in the Activity class objects retrieved when looping through the iterator?

Screenshot 2024-01-20 at 1 00 06 PM

lwasser avatar Jan 20 '24 20:01 lwasser

Thank you for this detailed investigation!

I think that it will be hard to say what breaks when one returns the right class. But perhaps that breakage will be good! If somebody tries to use the calories field, it will be a hard error instead of the uninitialized values.

I'm not sure whether there is a soft approach to this that would give users tone to react before the breaking change, though.

martin-ueding avatar Jan 20 '24 21:01 martin-ueding

I'm not sure whether there is a soft approach to this that would give users [time] to react before the breaking change, though.

Yes, as an intermediate version we can introduce the correct subclasses that reflect Strava's response types, but catch AttributeErrors from missing attributes with a __getattr__() implementation that logs a warning and returns None (reflecting the current state of affairs).

I'd like to do this after we moved to a version 2.0 with removed backward compatibility hacks. That will make the above changes much easier.

jsamoocha avatar Jan 21 '24 14:01 jsamoocha

cool, i'll leave this open. maybe i can create a set of milestones related to post version 2.0 and we can at some point make a 2.0 migration set of milestone issues as well.

lwasser avatar Jan 28 '24 01:01 lwasser