Add must_close() decorator, use it in nilmdb

Warns at runtime if a class's close() method wasn't called before the
object was destroyed.
This commit is contained in:
Jim Paris 2012-12-31 17:17:57 -05:00
parent 19c846c71c
commit b0d76312d1
5 changed files with 93 additions and 9 deletions

View File

@ -94,6 +94,7 @@ class BisectableTable(object):
def __getitem__(self, index):
return self.table[index][0]
@nilmdb.utils.must_close()
class NilmDB(object):
verbose = 0
@ -135,14 +136,6 @@ class NilmDB(object):
else:
self.max_results = 16384
self.opened = True
def __del__(self):
if "opened" in self.__dict__: # pragma: no cover
fprintf(sys.stderr,
"error: NilmDB.close() wasn't called, path %s",
self.basepath)
def get_basepath(self):
return self.basepath
@ -151,7 +144,6 @@ class NilmDB(object):
self.con.commit()
self.con.close()
self.h5file.close()
del self.opened
def _sql_schema_update(self):
cur = self.con.cursor()

View File

@ -5,3 +5,4 @@ from .iteratorizer import Iteratorizer
from .serializer import Serializer
from .lrucache import lru_cache
from .diskusage import du
from .mustclose import must_close

42
nilmdb/utils/mustclose.py Normal file
View File

@ -0,0 +1,42 @@
# Class decorator that warns on stderr at deletion time if the class's
# close() member wasn't called.
from nilmdb.utils.printf import *
import sys
def must_close(errorfile = sys.stderr):
def decorator(cls):
def dummy(*args, **kwargs):
pass
if "__init__" not in cls.__dict__:
cls.__init__ = dummy
if "__del__" not in cls.__dict__:
cls.__del__ = dummy
if "close" not in cls.__dict__:
cls.close = dummy
orig_init = cls.__init__
orig_del = cls.__del__
orig_close = cls.close
def __init__(self, *args, **kwargs):
ret = orig_init(self, *args, **kwargs)
self.__dict__["_must_close"] = True
return ret
def __del__(self):
if "_must_close" in self.__dict__:
fprintf(errorfile, "error: %s.close() wasn't called!\n",
self.__class__.__name__)
return orig_del(self)
def close(self, *args, **kwargs):
del self._must_close
return orig_close(self)
cls.__init__ = __init__
cls.__del__ = __del__
cls.close = close
return cls
return decorator

View File

@ -10,6 +10,7 @@ cover-erase=
##cover-branches= # need nose 1.1.3 for this
stop=
verbosity=2
#tests=tests/test_mustclose.py
#tests=tests/test_lrucache.py
#tests=tests/test_cmdline.py
#tests=tests/test_layout.py

48
tests/test_mustclose.py Normal file
View File

@ -0,0 +1,48 @@
import nilmdb
from nilmdb.utils.printf import *
import nose
from nose.tools import *
from nose.tools import assert_raises
from test_helpers import *
import sys
import cStringIO
err = cStringIO.StringIO()
@nilmdb.utils.must_close(errorfile = err)
class Foo:
def __init__(self):
fprintf(err, "Init\n")
def __del__(self):
fprintf(err, "Deleting\n")
def close(self):
fprintf(err, "Closing\n")
class TestMustClose(object):
def test(self):
# Note: this test might fail if the Python interpreter doesn't
# garbage collect the object (and call its __del__ function)
# right after a "del x".
x = Foo()
del x
eq_(err.getvalue(),
"Init\n"
"error: Foo.close() wasn't called!\n"
"Deleting\n")
err.truncate(0)
y = Foo()
y.close()
del y
eq_(err.getvalue(),
"Init\n"
"Closing\n"
"Deleting\n")