When is a decorator (class) object destroyed?
<p>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 :</p>

<pre><code> 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
</code></pre>

<p>and for example I then call this function:</p>

<pre><code>@session
def RUN(command,session):
    result=session.run(command)
    return(result)
</code></pre>

<p>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:</p>

<pre><code>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") 
</code></pre>

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

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

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

<pre><code>@session
def MATCH(*args,**kwargs):
    pass
</code></pre>

<p>would the session object be the same ?</p>

<h2>How to</h2>

<p>Here is a template to create a decorator with parameters using a function:</p>

<pre><code>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
</code></pre>

<p>The usage is as follow:</p>

<pre><code>@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
</code></pre>

<p>With a class, it is more straightforward:</p>

<pre><code>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
</code></pre>

<h2>Answer</h2>

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

<pre><code>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
</code></pre>

<p>Notes:</p>

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

<h2>Reference</h2>

<p>See: <a href="http://stackoverflow.com/questions/739654">How to make a chain of function decorators in Python?</a></p>

python database-connection decorator python-decorators