From b0d76312d121e4b7445ab3ce9d2a82748049f796 Mon Sep 17 00:00:00 2001 From: Jim Paris Date: Mon, 31 Dec 2012 17:17:57 -0500 Subject: [PATCH] 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. --- nilmdb/nilmdb.py | 10 +------- nilmdb/utils/__init__.py | 1 + nilmdb/utils/mustclose.py | 42 ++++++++++++++++++++++++++++++++++ setup.cfg | 1 + tests/test_mustclose.py | 48 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 nilmdb/utils/mustclose.py create mode 100644 tests/test_mustclose.py diff --git a/nilmdb/nilmdb.py b/nilmdb/nilmdb.py index b5b6eb2..3c6691d 100644 --- a/nilmdb/nilmdb.py +++ b/nilmdb/nilmdb.py @@ -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() diff --git a/nilmdb/utils/__init__.py b/nilmdb/utils/__init__.py index ef6a21a..4864b0e 100644 --- a/nilmdb/utils/__init__.py +++ b/nilmdb/utils/__init__.py @@ -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 diff --git a/nilmdb/utils/mustclose.py b/nilmdb/utils/mustclose.py new file mode 100644 index 0000000..80bef5c --- /dev/null +++ b/nilmdb/utils/mustclose.py @@ -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 diff --git a/setup.cfg b/setup.cfg index 7ba66d6..85e487a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tests/test_mustclose.py b/tests/test_mustclose.py new file mode 100644 index 0000000..780f824 --- /dev/null +++ b/tests/test_mustclose.py @@ -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")