Browse Source

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.
tags/replace-pytables
Jim Paris 9 years ago
parent
commit
b0d76312d1
5 changed files with 93 additions and 9 deletions
  1. +1
    -9
      nilmdb/nilmdb.py
  2. +1
    -0
      nilmdb/utils/__init__.py
  3. +42
    -0
      nilmdb/utils/mustclose.py
  4. +1
    -0
      setup.cfg
  5. +48
    -0
      tests/test_mustclose.py

+ 1
- 9
nilmdb/nilmdb.py 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()


+ 1
- 0
nilmdb/utils/__init__.py 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
- 0
nilmdb/utils/mustclose.py 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

+ 1
- 0
setup.cfg 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
- 0
tests/test_mustclose.py 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")

Loading…
Cancel
Save