2

I have a class called Org, and I'm trying to access its method from multiple functions (that are defined outside of the class). I'm calling main() first, followed by discover_buildings(). The main() executes without error, however, I get AttributeError: 'Org' has no attribute 'headers' error after I call discover_buildings(). What is it that I'm doing wrong? (I was expecting the headers attribute to be shared across the different methods)

class Org(object):

    def __init__(self, client_id, client_secret, grant_type='client_credentials'):
        self.grant_type = grant_type
        self.client_id = client_id
        self.client_secret = client_secret
        self.url = CI_A_URL

    def auth(self):
        """ authenticate with bos """
        params = {
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'grant_type': self.grant_type
        }
        r = requests.post(self.url + 'o/token/', data=params)
        if r.status_code == 200:
            self.access_token = r.json()['access_token']
            self.headers = {
                'Authorization': 'Bearer %s' %self.access_token,
                'Content-Type': 'application/json',
            }
        else:
            logging.error(r.content)
            r.raise_for_status()

    def get_buildings(self, perPage=1000):
        params = {
            'perPage': perPage
        }

        r = requests.get(self.url + 'buildings/', params=params, headers=self.headers)
        result = r.json().get('data')
        if r.status_code == 200:
            buildings_dict = {i['name']: i['id'] for i in result}
            sheet_buildings['A1'].value = buildings_dict
        else:
            logging.error(r.content)
            r.raise_for_status()


client_id = 'xxx'
client_secret = 'yyy'
gateway_id = 123
o = Org(client_id, client_secret)

def discover_buildings():
    return o.get_buildings()

def main():
    return o.auth()

Thanks, in advance, for your help!

JPcodes
  • 119
  • 6
  • Consider making headers, or the `token` part of it a *property* and call auth from it to acquire that token. https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work-in-python – JL Peyret Dec 17 '21 at 16:54

3 Answers3

1

the problem is the way you define "discover_buildings" you define it first with "o" just initialised not after the authentication.

to handle this:

  1. rewrite discover to take 'o' as a parameter

    or

  2. check first to see 'o' has 'headers' if not authenticate 'o' and do the rest

     def discover_buildings():
         if not getattr(o, 'headers'):
             o.auth()
         return o.get_buildings()
    
1

Try using a property to calculate headers whenever you need it and then cache it.


    def auth(self):
        """ authenticate with bos """
        #  you might want to isolate `token` into a nested @property token
        params = {
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'grant_type': self.grant_type
        }

        # note assignment to `_headers`, not `headers`



        r = requests.post(self.url + 'o/token/', data=params)
        if r.status_code == 200:
            self._access_token = r.json()['access_token']


        #   
            self._headers = { # 
                'Authorization': 'Bearer %s' %self._access_token,
                'Content-Type': 'application/json',
            }
        else:
            logging.error(r.content)
            r.raise_for_status()

    #cache after the first time.
    _headers = None
    
    @property
    def headers(self):
        """ call auth when needed
        you might want to isolate `token`
        into its own property, allowing different
        headers to use the same token lookup
        """
        if self._headers is None:
            self.auth()
        return self._headers
    
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • You're welcome. Do keep in mind the idea of isolating `token` into a property rather than `headers`. I might have a class that makes 3 different requests, with needs for different headers. In fact, you might have 3 or 4 classes that all need to share the same token (which could be done by a Mixin or shared cache). But you can still calculate token on demand via properties. – JL Peyret Dec 17 '21 at 18:00
0

You didn't define self.headers. You need to run o.auth() (or define self.headers) before you run o.get_buildings().

Koviubi56
  • 127
  • 1
  • 7