Permalink
Browse files

Bug Fixes

~~~~~~~~~

- Work around failure to retry ConflictError properly at commit time by the
  ``transaction`` 1.2.0 package.  See
  https://mail.zope.org/pipermail/zodb-dev/2012-March/014603.html for
  details.

Testing
~~~~~~~

- No longer tested under Python 2.5 by ``tox.ini`` (and therefore no longer
  tested under 2.5 by the Pylons Jenkins server).  The package may still work
  under 2.5, but automated tests will no longer show breakage when it changes
  in ways that break 2.5 support.

- Squash test deprecation warnings under Python 3.2.

- Move to new docs regime.

- Add dev and docs aliases to setup.cfg.
  • Loading branch information...
1 parent 84406b5 commit b12229332b907b31d4ae78c26fb8ea6b840aa331 @mcdonc mcdonc committed Mar 28, 2012
Showing with 175 additions and 44 deletions.
  1. +22 −1 CHANGES.txt
  2. +1 −0 docs/.gitignore
  3. +3 −5 docs/Makefile
  4. +1 −1 docs/_themes
  5. +35 −13 docs/conf.py
  6. +35 −1 pyramid_tm/__init__.py
  7. +64 −11 pyramid_tm/tests.py
  8. +5 −0 setup.cfg
  9. +7 −0 setup.py
  10. +2 −12 tox.ini
View
@@ -1,3 +1,24 @@
+Next release
+------------
+
+Bug Fixes
+~~~~~~~~~
+
+- Work around failure to retry ConflictError properly at commit time by the
+ ``transaction`` 1.2.0 package. See
+ https://mail.zope.org/pipermail/zodb-dev/2012-March/014603.html for
+ details.
+
+Testing
+~~~~~~~
+
+- No longer tested under Python 2.5 by ``tox.ini`` (and therefore no longer
+ tested under 2.5 by the Pylons Jenkins server). The package may still work
+ under 2.5, but automated tests will no longer show breakage when it changes
+ in ways that break 2.5 support.
+
+- Squash test deprecation warnings under Python 3.2.
+
0.3 (2011-09-27)
----------------
@@ -12,7 +33,7 @@ Features
exception to abort the transaction.
- The transaction manager will now retry retryable exceptions (such as a ZODB
- conflict error) if ``pyramid.attempts`` is configured to be more than the
+ conflict error) if ``tm.attempts`` is configured to be more than the
default of ``1``. See the ``Retrying`` section of the documentation.
- Python 3.2 compatibility (requires Pyramid 1.3dev+).
View
@@ -1,2 +1,3 @@
_build/
+_themes/
.static
View
@@ -2,7 +2,7 @@
#
# You can set these variables from the command line.
-SPHINXOPTS =
+SPHINXOPTS = -W
SPHINXBUILD = sphinx-build
PAPER =
@@ -25,7 +25,7 @@ help:
clean:
-rm -rf _build/*
-html: _themes/
+html:
mkdir -p _build/html _build/doctrees
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
@echo
@@ -47,7 +47,7 @@ pickle:
web: pickle
-htmlhelp: _themes
+htmlhelp:
mkdir -p _build/htmlhelp _build/doctrees
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
@echo
@@ -84,5 +84,3 @@ epub:
@echo
@echo "Build finished. The epub file is in _build/epub."
-_themes:
- cd ..; git submodule update --init; cd docs
View
@@ -19,16 +19,40 @@
import sys, os
-parent = os.path.dirname(os.path.dirname(__file__))
-sys.path.append(os.path.abspath(parent))
-wd = os.getcwd()
-os.chdir(parent)
-os.system('%s setup.py test -q' % sys.executable)
-os.chdir(wd)
+# Add and use Pylons theme
+if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers
+ from subprocess import call, Popen, PIPE
+
+ p = Popen('which git', shell=True, stdout=PIPE)
+ git = p.stdout.read().strip()
+ cwd = os.getcwd()
+ _themes = os.path.join(cwd, '_themes')
+
+ if not os.path.isdir(_themes):
+ call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git',
+ '_themes'])
+ else:
+ os.chdir(_themes)
+ call([git, 'checkout', 'master'])
+ call([git, 'pull'])
+ os.chdir(cwd)
+
+ sys.path.append(os.path.abspath('_themes'))
+
+ parent = os.path.dirname(os.path.dirname(__file__))
+ sys.path.append(os.path.abspath(parent))
+ wd = os.getcwd()
+ os.chdir(parent)
+ os.system('%s setup.py test -q' % sys.executable)
+ os.chdir(wd)
+
+ for item in os.listdir(parent):
+ if item.endswith('.egg'):
+ sys.path.append(os.path.join(parent, item))
-for item in os.listdir(parent):
- if item.endswith('.egg'):
- sys.path.append(os.path.join(parent, item))
+html_theme_path = ['_themes']
+html_theme = 'pyramid'
+html_theme_options = dict(github_url='http://github.com/Pylons/pyramid_tm')
# General configuration
# ---------------------
@@ -79,6 +103,8 @@
# searched for source files.
#exclude_dirs = []
+exclude_patterns = ['_themes/README.rst',]
+
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
@@ -102,10 +128,6 @@
# -----------------------
# Add and use Pylons theme
-sys.path.append(os.path.abspath('_themes'))
-html_theme_path = ['_themes']
-html_theme = 'pyramid'
-html_theme_options = dict(github_url='http://github.com/Pylons/pyramid_tm')
# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
View
@@ -35,6 +35,40 @@ class AbortResponse(Exception):
def __init__(self, response):
self.response = response
+# work around broken "attempts" method of TransactionManager in transaction
+# 1.2.0
+def _attempts(manager, number=3):
+ assert number > 0
+ while number:
+ number -= 1
+ if number:
+ yield Attempt(manager)
+ else:
+ yield manager
+
+class Attempt(object):
+
+ def __init__(self, manager):
+ self.manager = manager
+
+ def __enter__(self):
+ return self.manager.__enter__()
+
+ def __exit__(self, t, v, tb):
+ if v is None:
+ try:
+ self.manager.commit()
+ except:
+ # this is what transaction 1.2.0 doesn't do (it doesn't
+ # catch exceptions raised by a commit)
+ retry = self.manager._retryable(*sys.exc_info()[:2])
+ self.manager.abort()
+ return retry
+ else:
+ retry = self.manager._retryable(t, v)
+ self.manager.abort()
+ return retry
+
def tm_tween_factory(handler, registry, transaction=transaction):
# transaction parameterized for testing purposes
old_commit_veto = registry.settings.get('pyramid_tm.commit_veto', None)
@@ -52,7 +86,7 @@ def tm_tween(request):
return handler(request)
try:
- for attempt in transaction.attempts(attempts):
+ for attempt in _attempts(transaction.manager, attempts):
with attempt as t:
# make_body_seekable will copy wsgi.input if necessary,
# otherwise it will rewind the copy to position zero
View
@@ -8,47 +8,47 @@ def _callFUT(self, response, request=None):
def test_it_true_500(self):
response = DummyResponse('500 Server Error')
- self.failUnless(self._callFUT(response))
+ self.assertTrue(self._callFUT(response))
def test_it_true_503(self):
response = DummyResponse('503 Service Unavailable')
- self.failUnless(self._callFUT(response))
+ self.assertTrue(self._callFUT(response))
def test_it_true_400(self):
response = DummyResponse('400 Bad Request')
- self.failUnless(self._callFUT(response))
+ self.assertTrue(self._callFUT(response))
def test_it_true_411(self):
response = DummyResponse('411 Length Required')
- self.failUnless(self._callFUT(response))
+ self.assertTrue(self._callFUT(response))
def test_it_false_200(self):
response = DummyResponse('200 OK')
- self.failIf(self._callFUT(response))
+ self.assertFalse(self._callFUT(response))
def test_it_false_201(self):
response = DummyResponse('201 Created')
- self.failIf(self._callFUT(response))
+ self.assertFalse(self._callFUT(response))
def test_it_false_301(self):
response = DummyResponse('301 Moved Permanently')
- self.failIf(self._callFUT(response))
+ self.assertFalse(self._callFUT(response))
def test_it_false_302(self):
response = DummyResponse('302 Found')
- self.failIf(self._callFUT(response))
+ self.assertFalse(self._callFUT(response))
def test_it_false_x_tm_commit(self):
response = DummyResponse('200 OK', {'x-tm':'commit'})
- self.failIf(self._callFUT(response))
+ self.assertFalse(self._callFUT(response))
def test_it_true_x_tm_abort(self):
response = DummyResponse('200 OK', {'x-tm':'abort'})
- self.failUnless(self._callFUT(response))
+ self.assertTrue(self._callFUT(response))
def test_it_true_x_tm_anythingelse(self):
response = DummyResponse('200 OK', {'x-tm':''})
- self.failUnless(self._callFUT(response))
+ self.assertTrue(self._callFUT(response))
class Test_tm_tween_factory(unittest.TestCase):
def setUp(self):
@@ -207,6 +207,55 @@ def test_it(self):
self.assertEqual(config.tweens,
[('pyramid_tm.tm_tween_factory', EXCVIEW, None)])
+class TestAttempt(unittest.TestCase):
+ def _makeOne(self, manager):
+ from pyramid_tm import Attempt
+ return Attempt(manager)
+
+ def test___enter__(self):
+ manager = DummyManager()
+ inst = self._makeOne(manager)
+ self.assertTrue(inst.__enter__() is manager)
+
+ def test_exit_v_is_not_None(self):
+ manager = DummyManager(retryable='abc')
+ inst = self._makeOne(manager)
+ result = inst.__exit__(None, 'f', None)
+ self.assertTrue(manager.aborted)
+ self.assertEqual(result, 'abc')
+
+ def test_exit_v_is_None_commit_does_not_raise(self):
+ manager = DummyManager(retryable='abc')
+ inst = self._makeOne(manager)
+ result = inst.__exit__(None, None, None)
+ self.assertTrue(manager.committed)
+ self.assertEqual(result, None)
+
+ def test_exit_v_is_None_commit_raises(self):
+ manager = DummyManager(toraise=ValueError, retryable='abc')
+ inst = self._makeOne(manager)
+ result = inst.__exit__(None, None, None)
+ self.assertTrue(manager.aborted)
+ self.assertEqual(result, 'abc')
+
+class DummyManager(object):
+ def __init__(self, toraise=None, retryable=False):
+ self.toraise = toraise
+ self.retryable = retryable
+
+ def __enter__(self):
+ return self
+
+ def commit(self):
+ if self.toraise:
+ raise self.toraise
+ self.committed = True
+
+ def abort(self):
+ self.aborted = True
+
+ def _retryable(self, t, v):
+ return self.retryable
class Dummy(object):
def __init__(self, **kwargs):
@@ -231,6 +280,10 @@ def __init__(self, doomed=False):
self.committed = 0
self.aborted = 0
+ @property
+ def manager(self):
+ return self
+
def isDoomed(self):
return self.doomed
View
@@ -7,3 +7,8 @@ where=pyramid_tm
nocapture=1
cover-package=pyramid_tm
cover-erase=1
+
+[aliases]
+dev = develop easy_install pyramid_tm[testing]
+docs = develop easy_install pyramid_tm[docs]
+
View
@@ -28,6 +28,9 @@
'transaction',
]
+testing_extras = ['nose', 'coverage']
+docs_extras = ['Sphinx']
+
setup(name='pyramid_tm',
version='0.3',
description=('A package which allows Pyramid requests to join the '
@@ -56,4 +59,8 @@
tests_require=install_requires,
test_suite="pyramid_tm",
entry_points='',
+ extras_require = {
+ 'testing':testing_extras,
+ 'docs':docs_extras,
+ },
)
View
14 tox.ini
@@ -1,28 +1,18 @@
[tox]
envlist =
- py25,py26,py27,jython,pypy,cover
+ py26,py27,py32,pypy,cover
[testenv]
commands =
python setup.py test -q
-[testenv:jython]
-commands =
- jython setup.py test -q
-
[testenv:cover]
basepython =
python2.6
commands =
python setup.py nosetests --with-xunit --with-xcoverage
deps =
nose
- coverage==3.4
+ coverage
nosexcover
-
-# we separate coverage into its own testenv because a) "last run wins" wrt
-# cobertura jenkins reporting and b) pypy and jython can't handle any
-# combination of versions of coverage and nosexcover that i can find.
-# coverage==3.4 is required by nosexcover.
-

0 comments on commit b122293

Please sign in to comment.