6

Some background: I work in a large bank and I'm trying to re-use some Python modules, which I cannot change, only import. I also don't have the option of installing any new utilities/functions etc (running Python 2.6 on Linux).

I've got this at present:

In my module:

from common.databaseHelper import BacktestingDatabaseHelper

class mfReportProcess(testingResource):
    def __init__(self):
        self.db = BacktestingDatabaseHelper.fromConfig('db_name')

One of the methods called within the 'testingResource' class has this:

 with self.db as handler:

which falls over with this:

with self.db as handler:
AttributeError: 'BacktestingDatabaseHelper' object has no attribute '__exit__'

and, indeed, there is no __exit__ method in the 'BacktestingDatabaseHelper' class, a class which I cannot change.

However, this code I'm trying to re-use works perfectly well for other apps - does anyone know why I get this error and no-one else? Is there some way of defining __exit__ locally?

Many thanks in advance.

EDITED to add:

I've tried to add my own class to setup DB access but can't get it to work - added this to my module:

class myDB(BacktestingDatabaseHelper): 
    def __enter__(self): 
        self.db = fromConfig('db_name') 
    def __exit__(self): 
        self.db.close() 

and added:

self.db = myDB 

into my init attribute for my main class but I get this error:

with self.db as handler:
TypeError: unbound method __enter__() must be called with myDB instance as first argument (got nothing instead)

Any suggestions as to how to do this properly?

2
  • 3
    They are using a different version of the module or they are not using BacktestingDatabaseHelper as a context manager Commented May 24, 2012 at 11:44
  • 2
    You would have to know what the real __enter__ and __exit__ do, and do the same. If the same code works for other people in your company, I really suggest you to check the version and contents of each the common modules. Commented May 24, 2012 at 13:22

5 Answers 5

11

Using the with protocol assumes that the object used in with implements the context manager protocol.

Basically this means that the class definition should have __enter__() and __exit__() methods defined. If you use an object without these, python will throw an AttributeError complaining about the missing __exit__ attribute.

Sign up to request clarification or add additional context in comments.

Comments

4

The error means that BacktestingDatabaseHelper is not designed to be used in a with statement. Sounds like the classes testingResource and BacktestingDatabaseHelper are not compatible with each other (perhaps your version of common.databaseHelper is out of date).

3 Comments

I'm coming round to the idea that my version is wrong/old but waiting for a member of the dev team to advise.
Aaggh - the dev guy now tells me he installed the wrong package for me!! Very sorry to all and many thanks for all your answers.
@user996166; so put that as 'EDIT:' in the first line of your question!!
3

As you cannot change the with statement, you must add a class deriving from BacktestingDatabaseHelper which adds appropriate __enter__() and __exit__() functions and use this instead.

Here is an example which tries to be as close to the original as possible:

class myDB(BacktestingDatabaseHelper): 
    def __enter__(self): 
        return self
    def __exit__(self): 
        self.db.close()
    def fromConfig(self, name):
        x = super(myDB, self).fromConfig(name)
        assert isinstance(x, BacktestingDatabaseHelper)
        x.__class__ = myDB # not sure if that really works
[...]
self.db=myDB.fromConfig('tpbp')

The problem is, however, that I am not sure what the __enter__ is supposed to return. If you take MySQLdb, for example, the context manager of the connection creates a cursor representing one transaction. If that's the case here as well, wou have to work out something else...

3 Comments

@user996166 I added an example how it could work - maybe. self.db = myDB just assigns the class. Maybe you could as well try with self.db = myDB(), although I doubt if that works.
Unfortunately, I have to use the common version of 'fromConfig' (which I can't change) as it has important parameters/variables to setup.
In my example above, you use it (via the super stuff) and just change its result's class.
2

You might want to try the contextlib.contextmanager decorator to wrap your object so that it supports the context manager protocol.

Comments

2

The 'with' keyword is basically a shortcut for writing out:

try:
    // Do something
finally:
    hander.__exit__()

which is useful if your handler object is using up resources (like, for example, an open file stream). It makes sure that no matter what happens in the 'do something' part, the resource is released cleanly.

In your case, your handler object doesn't have an __exit__ method, and so with fails. I would assume that other people can use BacktestingDatabaseHelper because they're not using with.

As for what you can do now, I would suggest forgetting with and using try ... finally instead, rather than trying to add your own version of __exit__ to the object. You'll just have to make sure you release the handler properly (how you do this will depend on how BacktestingDatabaseHelper is supposed to be used), e.g.

try:
    handler = self.db
    // do stuff
finally:
    handler.close()

Edit: Since you can't change it, you should do something like @Daniel Roseman suggests to wrap BacktestingDatabaseHelper. Depending on how best to clean up BacktestingDatabaseHelper (as above), you can write something like:

from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

and use this as:

class mfReportProcess(testingResource):
    def __init__(self):
        self.db = closing(BacktestingDatabaseHelper.fromConfig('db_name'))

(this is directly from the documentation).

8 Comments

Unfortunately, that 'with' line is in part of the common code that I can't change, so I have to use it.
(My response was too long for a comment, so I've edited my answer.)
Besides, I am not sure if it is just closing what the context manager is up to do, or if it servers to something else (transaction management etc.)
Besides as well, you approach only works if the with stuff is executed at most once.
@karaken12 Look for example at threading.Lock: you can use it as often as you want, and it works as a context manager. CMs are not only for closing, hey are useful for other stuff as well. In the case of closing, you are right, though.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.