Skip to content

Commit

Permalink
Close the underlying iterator.
Browse files Browse the repository at this point in the history
Fixes #4.

Also add PyPy support.

Modernize the .travis.yml: sudo false, pip caching, same testrunner as
tox.ini.

Pin ZODB/ZEO to old versions because ServerZlibStorage is broken for
ZODB 5. Will address in a subsequent PR.
  • Loading branch information
jamadden committed Jan 5, 2017
1 parent 76a57ea commit 8bf5620
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 24 deletions.
12 changes: 9 additions & 3 deletions .travis.yml
@@ -1,12 +1,18 @@
sudo: false
language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
- pypy-5.4.1
install:
- pip install zc.buildout
- buildout
- pip install -U pip setuptools
- pip install -U -e .[test] zope.testrunner
script:
- bin/test -v1j99
- zope-testrunner --test-path=src -v1j99
notifications:
email: false
cache: pip
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
25 changes: 19 additions & 6 deletions CHANGES.rst
@@ -1,8 +1,21 @@
Changes
=======
=========
Changes
=========

1.2.0 (unreleased)
==================

- Add support for Python 3.6 and PyPy.

- Restrict ZODB dependency to less than 5.0. ServerZlibStorage
currently doesn't work with ZEO 5.
(https://github.com/zopefoundation/zc.zlibstorage/issues/5).

- Close the underlying iterator used by the ``iterator`` wrapper when
it is closed. (https://github.com/zopefoundation/zc.zlibstorage/issues/4)

1.1.0 (2016-08-03)
------------------
==================

- Fixed an incompatibility with ZODB5. The previously optional and
ignored version argument to the database ``invalidate`` method is now
Expand All @@ -11,16 +24,16 @@ Changes
- Drop Python 2.6, 3.2, and 3.3 support. Added Python 3.4 and 3.5 support.

1.0.0 (2015-11-11)
------------------
==================

- Python 3 support contributed by Christian Tismer.

0.1.1 (2010-05-26)
------------------
==================

- Fixed a packaging bug.

0.1.0 (2010-05-20)
------------------
==================

Initial release
8 changes: 6 additions & 2 deletions setup.py
Expand Up @@ -13,8 +13,8 @@
##############################################################################
name, version = 'zc.zlibstorage', '1.1.0'

install_requires = ['setuptools', 'ZODB', 'zope.interface']
extras_require = dict(test=['zope.testing', 'manuel', 'ZEO'])
install_requires = ['setuptools', 'ZODB < 5', 'zope.interface']
extras_require = dict(test=['zope.testing', 'manuel', 'ZEO[test] < 5'])

entry_points = """
"""
Expand All @@ -41,9 +41,13 @@ def read(filename):
long_description=long_description,
description=long_description.split('\n')[1],
classifiers=[
"Programming Language :: Python",
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
packages=[name.split('.')[0], name],
namespace_packages=[name.split('.')[0]],
Expand Down
32 changes: 30 additions & 2 deletions src/zc/zlibstorage/__init__.py
Expand Up @@ -95,8 +95,7 @@ def restore(self, oid, serial, data, version, prev_txn, transaction):
oid, serial, self._transform(data), version, prev_txn, transaction)

def iterator(self, start=None, stop=None):
for t in self.base.iterator(start, stop):
yield Transaction(t)
return _Iterator(self.base.iterator(start, stop))

def storeBlob(self, oid, oldserial, data, blobfilename, version,
transaction):
Expand Down Expand Up @@ -156,6 +155,35 @@ class ServerZlibStorage(ZlibStorage):
'iterator', 'storeBlob', 'restoreBlob', 'record_iternext',
)

class _Iterator(object):
# A class that allows for proper closing of the underlying iterator
# as well as avoiding any GC issues.
# (https://github.com/zopefoundation/zc.zlibstorage/issues/4)

def __init__(self, base_it):
self._base_it = base_it

def __iter__(self):
return self

def __next__(self):
return Transaction(next(self._base_it))

next = __next__

def close(self):
try:
base_close = self._base_it.close
except AttributeError:
pass
else:
base_close()
finally:
self._base_it = iter(())

def __getattr__(self, name):
return getattr(self._base_it, name)

class Transaction(object):

def __init__(self, trans):
Expand Down
71 changes: 65 additions & 6 deletions src/zc/zlibstorage/tests.py
Expand Up @@ -38,6 +38,10 @@

import zc.zlibstorage

def _copy(dest, src):
with open(src, 'rb') as srcf:
with open(dest, 'wb') as destf:
destf.write(srcf.read())

def test_config():
r"""
Expand Down Expand Up @@ -185,7 +189,7 @@ def test_mixed_compressed_and_uncompressed_and_packing():
- using the compressed storage:
>>> _ = open('data.fs.save', 'wb').write(open('data.fs', 'rb').read())
>>> _copy('data.fs.save', 'data.fs')
>>> db = ZODB.DB(zc.zlibstorage.ZlibStorage(
... ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs')))
>>> db.pack()
Expand All @@ -195,7 +199,7 @@ def test_mixed_compressed_and_uncompressed_and_packing():
- using the storage in non-compress mode:
>>> _ = open('data.fs', 'wb').write(open('data.fs.save', 'rb').read())
>>> _copy('data.fs', 'data.fs.save')
>>> db = ZODB.DB(zc.zlibstorage.ZlibStorage(
... ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs'),
... compress=False))
Expand All @@ -207,7 +211,7 @@ def test_mixed_compressed_and_uncompressed_and_packing():
- using the server storage:
>>> _ = open('data.fs', 'wb').write(open('data.fs.save', 'rb').read())
>>> _copy('data.fs', 'data.fs.save')
>>> db = ZODB.DB(zc.zlibstorage.ServerZlibStorage(
... ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs'),
... compress=False))
Expand All @@ -219,7 +223,7 @@ def test_mixed_compressed_and_uncompressed_and_packing():
- using the server storage in non-compress mode:
>>> _ = open('data.fs', 'wb').write(open('data.fs.save', 'rb').read())
>>> _copy('data.fs', 'data.fs.save')
>>> db = ZODB.DB(zc.zlibstorage.ServerZlibStorage(
... ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs'),
... compress=False))
Expand Down Expand Up @@ -400,6 +404,57 @@ def getConfig(self):
</serverzlibstorage>
"""

class TestIterator(unittest.TestCase):

def test_iterator_closes_underlying_explicitly(self):
# https://github.com/zopefoundation/zc.zlibstorage/issues/4

class Storage(object):

storage_value = 42
iterator_closed = False

def registerDB(self, db):
pass

def iterator(self, start=None, stop=None):
return self

def __iter__(self):
return self

def __next__(self):
return self

next = __next__

def close(self):
self.iterator_closed = True

storage = Storage()
zstorage = zc.zlibstorage.ZlibStorage(storage)

it = zstorage.iterator()

# Make sure it proxies all attributes
self.assertEqual(42, getattr(it, 'storage_value'))

# Make sure it iterates (whose objects also proxy)
self.assertEqual(42, getattr(next(it), 'storage_value'))

# The real iterator is closed
it.close()

self.assertTrue(storage.iterator_closed)

# And we can't move on; the wrapper prevents it even though
# the underlying storage implementation is broken
self.assertRaises(StopIteration, next, it)

# We can keep closing it though
it.close()


def test_suite():
suite = unittest.TestSuite()
for class_ in (
Expand All @@ -415,8 +470,10 @@ def test_suite():
'zlibstoragetests.%s' % class_.__name__)
suite.addTest(s)

suite.addTest(unittest.makeSuite(TestIterator))

# The conflict resolution and blob tests don't exercise proper
# plumbing for libstorage because the sample data they use
# plumbing for zlibstorage because the sample data they use
# compresses to larger than the original. Run the tests again
# after monkey patching zlibstorage to compress everything.

Expand All @@ -442,6 +499,9 @@ def tearDown(self):
# Py3k renders bytes where Python2 used native strings...
(re.compile(r"b'"), "'"),
(re.compile(r'b"'), '"'),
# Older versions of PyPy2 (observed in PyPy2 5.4 but not 5.6)
# produce long integers (1L) where we expect normal ints
(re.compile(r"(\d)L"), r"\1")
])

suite.addTest(doctest.DocTestSuite(
Expand All @@ -454,4 +514,3 @@ def tearDown(self):
setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown
))
return suite

9 changes: 4 additions & 5 deletions tox.ini
@@ -1,11 +1,10 @@
[tox]
envlist =
py26,py27,py32,py33
py27,py34,py35,py36,pypy

[testenv]
deps =
zope.testing
manuel
ZEO
.[test]
zope.testrunner
commands =
python setup.py test -q
zope-testrunner --test-path=src -v1j99

0 comments on commit 8bf5620

Please sign in to comment.