Search code examples
pythonpropertiespython-descriptors

Reset property cached by descriptor for all instances of class


I want to share requests session among multiple class instances and also be able to reset the session for all such instances.

So I have the disconnect method for resetting the session for all instances:

import requests

class CachedSession:
    def __init__(self):
        self._initialized = None

    def __get__(self, *args, **kwargs):
        if self._initialized is None:
            self._initialized = self.connect()
        return self._initialized

    def connect(self):
        session = requests.session()
        session.headers = {}
        session.proxies = {}
        session.verify = False
        return session

class SomeApi:
    session = CachedSession()

    def __init__(self):
        self.api_key = '123'

    def disconnect(self):
        self.__class__.session.close()
        self.__class__.session = None

    def request(self):
        print(f'making request using session ID {id(self.session)}')

some_api1 = SomeApi()
some_api2 = SomeApi()

some_api1.request()
some_api1.request()
some_api2.request()

some_api1.disconnect()

some_api1.request()
some_api1.request()
some_api2.request()

# prints OK:
# making request using session ID 1291988425360
# making request using session ID 1291988425360
# making request using session ID 1291988425360
# making request using session ID 140726378118360
# making request using session ID 140726378118360
# making request using session ID 140726378118360

But is it the proper way to do it?

Having to use __class__ feels a little hackish, but if I remove it, only instance session will get reset.

It also does not feel entirely right that I use instance method (disconnect) to reset all other instances.

Then I do not like the fact that connect and disconnect belong to different classes, ideally I would like to have them both on the CachedSession descriptor (but how do I call disconnect then?)


Solution

  • To implement CachedSession with a singleton instance you should define it as a class property

    Having to use __class__ feels a little hackish, but if I remove it, only instance session will get reset.

    Instead of __class__ property you also could define the method as a @classmethod like below

    class CachedSession:
        _session = None
    
        def __get__(self, *args, **kwargs):
            return self.connect()
    
        @classmethod
        def connect(cls):
            if not cls._session:
                cls._session = requests.session()
                cls._session.headers = {}
                cls._session.proxies = {}
                cls._session.verify = False
            return cls._session
    
        @classmethod
        def disconnect(cls):
            if cls._session:
                cls._session.close()
                cls._session = None
    

    Then I do not like the fact that connect and disconnect belong to different classes, ideally I would like to have them both on the CachedSession descriptor (but how do I call disconnect then?)

    As you can see the disconnect already is a @classmethod of CachedSession which means that it is able to change any class property as well as _session. So after disconnecting the session will be disconnected all instances of the class while __get__ have not been called yet. To disconnect the session just call CachedSession.disconnect().

    class SomeApi:
        session = CachedSession()
    
        def request(self):
            print(f'making request using session ID {id(self.session)}')
    
    
    some_api1 = SomeApi()
    some_api2 = SomeApi()
    
    some_api1.request()  # making request using session ID 139839142708616
    some_api1.request()  # making request using session ID 139839142708616
    some_api2.request()  # making request using session ID 139839142708616
    
    CachedSession.disconnect()
    
    some_api1.request()  # making request using session ID 139839142240040
    some_api1.request()  # making request using session ID 139839142240040
    some_api2.request()  # making request using session ID 139839142240040