AppDameon - PureGym Attendance

Hi all,

I am trying to run a Python Script for getting the live count of members at my gym. I am able to run the script fine on my own laptop. However I am unsure as to how to properly get it running in AppDameon.

The original Python Script is from here: GitHub Link

Any guidance on this would be very appreciated.

So far this is what I have tried:

Apps.yaml:

puregym:
  module: puregym
  class: PuregymAPIClient
  email: [WITHHELD FOR PRIVACY]
  pin: [WITHHELD FOR PRIVACY]

PureGym.py:

import requests
import textdistance
import appdaemon.plugins.hass.hassapi as hass


class PuregymAPIClient(hass.Hass):
    headers = {'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'PureGym/1523 CFNetwork/1312 Darwin/21.0.0'}
    authed = False
    home_gym_id = None
    gyms = None
    
    def initialize(self):
        self.login(self.args["email"],self.args["pin"])


    def login(self, email, pin):
        self.session = requests.session()
        data = {
            'grant_type': 'password',
            'username': 'email',
            'password': 'pin',
            'scope': 'pgcapi',
            'client_id': 'ro.client'
        }
        response = self.session.post('https://auth.puregym.com/connect/token', headers=self.headers, data=data)
        if response.status_code == 200:
            self.auth_json = response.json()
            self.authed = True
            self.headers['Authorization'] = 'Bearer '+self.auth_json['access_token']
        else:
            return response.raise_for_status()
    
    def get_list_of_gyms(self):
        if not self.authed:
            return PermissionError('Not authed: call login(email, pin)')
        response = self.session.get(f'https://capi.puregym.com/api/v1/gyms/', headers=self.headers)
        if response.status_code == 200:
            self.gyms = {i['name'].replace(' ', '').replace('-', '').lower(): i['id'] for i in response.json()}
        else:
            return ValueError('Response '+str(response.status_code))
    
    def get_gym(self, gym_name):
        """returns corrected gym name and its ID"""
        gym_name = gym_name.replace(' ', '').replace('-', '').lower()
        if self.gyms is None:
            self.get_list_of_gyms()
        return max(list(self.gyms.items()), key=lambda x: textdistance.levenshtein.similarity(gym_name, x[0]))

    def get_home_gym(self):
        if not self.authed:
            return PermissionError('Not authed: call login(email, pin)')

        response = self.session.get('https://capi.puregym.com/api/v1/member', headers=self.headers)
        if response.status_code == 200:
            self.home_gym_id = response.json()['homeGymId']
        else:
            return ValueError('Response '+str(response.status_code))
    
    def get_gym_attendance(self, gym, return_name=False):
        if not self.authed:
            return PermissionError('Not authed: call login(email, pin)')
        if gym is None:
            if self.home_gym_id is None:
                self.get_home_gym()
            gym_id = self.home_gym_id
        elif isinstance(gym, int):
            gym_id = gym
            gym = None
        else:
            gym, gym_id = self.get_gym(gym)  # name->id
        response = self.session.get(f'https://capi.puregym.com/api/v1/gyms/{gym_id}/attendance', headers=self.headers)
        if response.status_code == 200:
            n = response.json()['totalPeopleInGym']
            if return_name:
                return n, gym
            return n
        else:
            return response.raise_for_status()

if __name__ == '__main__':
    from argparse import ArgumentParser
    parser = ArgumentParser()
    parser.add_argument('email')
    parser.add_argument('pin')
    parser.add_argument('--gym', default=None)
    args = parser.parse_args()
    
    client = PuregymAPIClient()
    client.login(args.email, args.pin)
    print(client.get_gym_attendance(args.gym))

AppDameon Add-On Logs:


2022-05-06 11:16:30.063251 INFO AppDaemon: Reloading Module: /config/appdaemon/apps/puregym.py
2022-05-06 11:16:30.069682 INFO AppDaemon: Initializing app puregym using class PuregymAPIClient from module puregym 
2022-05-06 11:16:37.199941 WARNING puregym: ------------------------------------------------------------ 
2022-05-06 11:16:37.200396 WARNING puregym: Unexpected error running initialize() for puregym 2022-05-06 11:16:37.200620 WARNING puregym: ------------------------------------------------------------ 
2022-05-06 11:16:37.201298 WARNING puregym: Traceback (most recent call last): 
  File "/usr/lib/python3.9/site-packages/appdaemon/app_management.py", line 165, in initialize_app await utils.run_in_executor(self, init) 
  File "/usr/lib/python3.9/site-packages/appdaemon/utils.py", line 337, in run_in_executor response = future.result() File "/usr/lib/python3.9/concurrent/futures/thread.py", line 52, in run result = self.fn(*self.args, **self.kwargs) 
  File "/config/appdaemon/apps/puregym.py", line 13, in initialize self.login(self.args["email"],self.args["pin"]) 
  File "/config/appdaemon/apps/puregym.py", line 31, in login return response.raise_for_status() 
  File "/usr/lib/python3.9/site-packages/requests/models.py", line 953, in raise_for_status raise HTTPError(http_error_msg, response=self) requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://auth.puregym.com/connect/token 
2022-05-06 11:16:37.201611 WARNING puregym: ------------------------------------------------------------ 
2022-05-06 11:16:37.202664 WARNING AppDaemon: Excessive time spent in utility loop: 7149.0ms, 7149.0ms in check_app_updates(), 0.0ms in other

This is not written as an Appdaemon app. What I mean by that is appdaemon is a program used to automate python code. I don’t see any “Automation” of python code in this code.

You will need to first define what kind of “Automation” you are trying to do. Example would be are you trying run this code once a day and then try to output it somewhere? Is there some kind of trigger you had in mind for running this script like when your location is in a certain zone or when you press a button?

It really looks like you are trying to run a regular python script which you don’t need appdaemon for. In fact you can do that in Homeassistant with python scripts.

If you’re new to python and just using appdaemon to try to pass arguments there a better ways ,but if you insist on appdaemon you should look at Passing Arguments to Apps to improve your code.

Yes, correct, I would like this run either when I leave the house between particular hours or just to run every weekday at a particular time. I was hoping that the output of that script could be passed to a notification via the Home Assistant Mobile App.

I tried to run it with python scripts, however the script has some dependencies which I cannot get for use with the Python Scripts integration as it does not support Python Imports.

What do you suggest I can do?

You you want will look much more like this:

puregym:
    module: puregym
    class: PuregymAPIClient
    log_level: DEBUG
    data:
        grant_type: 'password'
        username: 'email'
        password: 'pin'
        scope: 'pgcapi'
        client_id: 'ro.client'
class PuregymAPIClient(hass.Hass):
    def initialize(self):
        self.log(f'__function__: Starting {__name__}', level='INFO')
        self.data = self.args.data('data')
        if self.data:
            self.log(data, level='DEBUG')
        else:
            self.error('No data provided')
            return
        self.headers = {'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'PureGym/1523 CFNetwork/1312 Darwin/21.0.0'}
        self.authed = False
        self.home_gym_id = None
        self.gyms = None


        try:
            self.login()
        except Exception as Error:
            self.error(Error)

        try:
            self.log(self.get_gym_attendance)
        except Exception as Error:
            self.error(Error)      


    def login(self):
        self.log('__function__: Logging in', level="DEBUG")
        self.session = requests.session()
        response = self.session.post('https://auth.puregym.com/connect/token', headers=self.headers, data=self.data)
        if response.status_code == 200:
            self.auth_json = response.json()
            self.authed = True
            self.headers['Authorization'] = 'Bearer '+self.auth_json['access_token']
        else:
            return response.raise_for_status()
    
    def get_list_of_gyms(self):
        self.log('__function__: Getting list of gyms', level="DEBUG")
        if not self.authed:
            return PermissionError('Not authed: call login(email, pin)')
        response = self.session.get(f'https://capi.puregym.com/api/v1/gyms/', headers=self.headers)
        if response.status_code == 200:
            self.gyms = {i['name'].replace(' ', '').replace('-', '').lower(): i['id'] for i in response.json()}
        else:
            return ValueError('Response '+str(response.status_code))
    
    def get_gym(self, gym_name):
        """returns corrected gym name and its ID"""
        self.log('__function__: Getting gym', level="DEBUG")
        gym_name = gym_name.replace(' ', '').replace('-', '').lower()
        if self.gyms is None:
            self.get_list_of_gyms()
        return max(list(self.gyms.items()), key=lambda x: textdistance.levenshtein.similarity(gym_name, x[0]))

    def get_home_gym(self):
        self.log('__function__: Getting home gym', level="DEBUG")
        if not self.authed:
            return PermissionError('Not authed: call login(email, pin)')

        response = self.session.get('https://capi.puregym.com/api/v1/member', headers=self.headers)
        if response.status_code == 200:
            self.home_gym_id = response.json()['homeGymId']
        else:
            return ValueError('Response '+str(response.status_code))
    
    def get_gym_attendance(self, gym, return_name=False):
        self.log('__function__: Getting home gym', level="DEBUG")
        if not self.authed:
            return PermissionError('Not authed: call login(email, pin)')
        if gym is None:
            if self.home_gym_id is None:
                self.get_home_gym()
            gym_id = self.home_gym_id
        elif isinstance(gym, int):
            gym_id = gym
            gym = None
        else:
            gym, gym_id = self.get_gym(gym)  # name->id
        response = self.session.get(f'https://capi.puregym.com/api/v1/gyms/{gym_id}/attendance', headers=self.headers)
        if response.status_code == 200:
            n = response.json()['totalPeopleInGym']
            if return_name:
                return n, gym
            return n
        else:
            return response.raise_for_status()

I don’t have your log in,so i can’t debug this ,but this is more inline with an appdaemon app. You want most of your variables in the initialize section. You probably want a Context manager for the login.

Yes, correct, I would like this run either when I leave the house between particular hours or just to run every weekday at a particular time. I was hoping that the output of that script could be passed to a notification via the Home Assistant Mobile App.

Now for doing stuff like running when you leave the house or every weekend appdaemon can easily do this ,but it requires knowledge in how to write in python. There are several good resources on how to write appdaemon apps on the documentation.