Search code examples
pythondatabase-connectiondecoratorpython-decorators

When is a decorator (class) object destroyed?


I am using a decorator to open and close a neo4j database session (and allow my decorated functions to run queries within that session). At first I used a decorator function :

 from neo4j.v1 import GraphDatabase, basic_auth
 def session(func,url="bolt://localhost:7474", user="neo4j",pwd="neo4j", *args, **kwargs):
     def operate(*args, **kwargs):
         driver = GraphDatabase.driver(url, auth=basic_auth(user,pwd))
         session=driver.session()
         kwargs["session"]=session 
         result=func(*args, **kwargs)
         session.close()
         return result
     return operate

and for example I then call this function:

@session
def RUN(command,session):
    result=session.run(command)
    return(result)

However, this opens and closes the session for every query which is resource consuming. Thus, I tried to create a decorator class, and store the session:

class session(object):
    def __init__(self, func, url="bolt://localhost", user="neo4j", pwd="neo4j"):
        try:
            driver = GraphDatabase.driver(url, auth=basic_auth(user, pwd))
            print("session opened")
        except:
            print("Exception during authentification")
            self.__exit__()
        else:
            session=driver.session()
            self.func=func
            self.SESSION=session

    def __call__(self, *args, **kwargs):
        kwargs["session"]=self.SESSION 
        result=self.func(*args, **kwargs)
        return result
    def __del__(self):
        print("del")
        try:
            self.SESSION.close()
            print("session closed")
        except:
            print("could not close session") 

This seems to work, as the "session opened" appears only once. But the session does not seem to close ("session close" is never printed).

So my first question is the following, how can I call the self.SESSION.close() at the destruction of the decorator ?

I'm also wondering if I understood well what my code is doing. When I call a decorated function such as RUN, is only one session object created? And what if I were to have an other decorated function MATCH

@session
def MATCH(*args,**kwargs):
    pass

would the session object be the same ?


Solution

  • How to

    Here is a template to create a decorator with parameters using a function:

    def decorator_maker(param1, param2):
        print("The parameters of my decorator are: {0} and {1}".format(param1, param2))
    
        def my_decorator(function_to_decorate):
            def wrapper(arg1, arg2):
                print("before call")
                result = function_to_decorate(arg1, arg2)
                print("after call")
                return result
    
            return wrapper
    
        return my_decorator
    

    The usage is as follow:

    @decorator_maker("hello", "How are you?")
    def my_function(arg1, arg2):
        print("The parameters of my function are: {0} and {1}".format(arg1, arg2))
        return arg1 + "-" + arg2
    

    With a class, it is more straightforward:

    class decorator_maker(object):
        def __init__(self, param1, param2):
            print("The parameters of my decorator are: {0} and {1}".format(param1, param2))
            self.param1 = param1
            self.param2 = param2
    
        def __call__(self, function_to_decorate):
            def wrapper(arg1, arg2):
                print("before call")
                result = function_to_decorate(arg1, arg2)
                print("after call")
                return result
    
            return wrapper
    

    Answer

    To open and close your session before/after the function call, you have to implement it in the wrapped function, like this:

    class with_session(object):
        def __init__(self, url="bolt://localhost", user="neo4j", pwd="neo4j"):
            self.url = url
            self.user = user
            self.pwd = pwd
    
        def __call__(self, f):
            def wrapper(*args, **kwargs):
                driver = GraphDatabase.driver(self.url, auth=basic_auth(self.user, self.pwd))
                kwargs["session"] = session = driver.session()
                try:
                    return f(*args, **kwargs)
                finally:
                    session.close()
    
            return wrapper
    

    Notes:

    • You can let the exception raises...
    • You should rename the decorator (like I did) to avoid shadowing the name "session".
    • This decorator is not a a context manager. For that you need to use __enter__ and __exit__, this is another use case...

    Reference

    See: How to make a chain of function decorators?