Skip to content

Commit

Permalink
Raise subclasses of KeyError (#5)
Browse files Browse the repository at this point in the history
* fix testing under py3.3 (could not find 'intid' in 'zope') and simplify

Bring in the .travis.yml changes from #3 and also do the same for tox.ini.

* Raise more informative KeyError subclasses.

* Make sure POSKeyError propagates.

Note that ``queryId`` now propagates this error; previously it was
hidden. This now matches the behaviour of ``queryObject`` which always
propagated.

Test this. I have 100% test coverage locally. Some minor refactoring of
the subscribers facilitates this (and reduces the nesting depth).

Also add a setup.cfg to indicate that we generate universal wheels.

* Use latest pypy3 on travis because of pip.
  • Loading branch information
jamadden committed Dec 6, 2016
1 parent d547f7a commit 12d653e
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 72 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
source = src
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ python:
- 3.4
- 3.5
- pypy
- pypy3
- pypy3.3-5.2-alpha1
install:
- pip install -U pip setuptools # need updated pip for caching
- pip install .[test]
Expand Down
12 changes: 12 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ Changes
4.2.0 (unreleased)
------------------

- Raise more informative KeyError subclasses from the utility when intids
or objects cannot be found. This distinguishes them from errors
raised by normal dictionaries or BTrees, and is useful in unit
testing or when persisting intids or sharing them among processes
for later or concurrent use.

- Propagate ``POSKeyError`` from ``queryId`` instead of returning the
default object. This exception indicates a corrupt database, not a
missing object. The ``queryObject`` function already behaved this way.

- Add support for Python 3.5.

- Drop support for Python 2.6.

- Add support for PyPy3 5.2.

- Stop depending on ZODB for anything except testing.

4.1.0 (2014-12-27)
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1
91 changes: 65 additions & 26 deletions src/zope/intid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@

from zope.intid.interfaces import IIntIds, IIntIdEvent
from zope.intid.interfaces import IntIdAddedEvent, IntIdRemovedEvent
from zope.intid.interfaces import IntIdMissingError, IntIdsCorruptedError, ObjectMissingError

try:
# POSKeyError is a subclass of KeyError; in the cases where we
# catch KeyError for an item missing from a BTree, we still
# want to propagate this exception that indicates a corrupt database
# (as opposed to a corrupt IntIds)
from ZODB.POSException import POSKeyError as _POSKeyError
except ImportError: # pragma: no cover (we run tests with ZODB installed)
# In practice, ZODB will probably be installed. But if not,
# then POSKeyError can never be generated, so use a unique
# exception that we'll never catch.
class _POSKeyError(BaseException):
pass

@implementer(IIntIds, IContained)
class IntIds(Persistent):
Expand Down Expand Up @@ -67,7 +81,12 @@ def __iter__(self):
return self.refs.iterkeys()

def getObject(self, id):
return self.refs[id]()
try:
return self.refs[id]()
except _POSKeyError:
raise
except KeyError:
raise ObjectMissingError(id)

def queryObject(self, id, default=None):
r = self.refs.get(id)
Expand All @@ -79,16 +98,20 @@ def getId(self, ob):
try:
key = IKeyReference(ob)
except (NotYet, TypeError, ValueError):
raise KeyError(ob)
raise IntIdMissingError(ob)

try:
return self.ids[key]
except _POSKeyError:
raise
except KeyError:
raise KeyError(ob)
raise IntIdMissingError(ob)

def queryId(self, ob, default=None):
try:
return self.getId(ob)
except _POSKeyError:
raise
except KeyError:
return default

Expand Down Expand Up @@ -131,10 +154,27 @@ def unregister(self, ob):
if key is None:
return

uid = self.ids[key]
del self.refs[uid]
try:
uid = self.ids[key]
except _POSKeyError:
raise
except KeyError:
raise IntIdMissingError(ob)

try:
del self.refs[uid]
except _POSKeyError:
raise
except KeyError:
# It was in self.ids, but not self.refs. Something is corrupted.
# We've always let this KeyError propagate, before cleaning up self.ids,
# meaning that getId(ob) will continue to work, but getObject(uid) will not.
raise IntIdsCorruptedError(ob, uid)
del self.ids[key]

def _utilities_and_key(ob):
utilities = tuple(getAllUtilitiesRegisteredFor(IIntIds))
return utilities, IKeyReference(ob, None) if utilities else None

@adapter(ILocation, IObjectRemovedEvent)
def removeIntIdSubscriber(ob, event):
Expand All @@ -143,18 +183,18 @@ def removeIntIdSubscriber(ob, event):
Removes the unique ids registered for the object in all the unique
id utilities.
"""
utilities = tuple(getAllUtilitiesRegisteredFor(IIntIds))
if utilities:
key = IKeyReference(ob, None)
# Register only objects that adapt to key reference
if key is not None:
# Notify the catalogs that this object is about to be removed.
notify(IntIdRemovedEvent(ob, event))
for utility in utilities:
try:
utility.unregister(key)
except KeyError:
pass
utilities, key = _utilities_and_key(ob)
if not utilities or key is None:
# Unregister only objects that adapt to key reference
return
# Notify the catalogs that this object is about to be removed.
notify(IntIdRemovedEvent(ob, event))
for utility in utilities:
try:
utility.unregister(key)
except KeyError:
# Silently ignoring all kinds corruption here
pass

@adapter(ILocation, IObjectAddedEvent)
def addIntIdSubscriber(ob, event):
Expand All @@ -163,16 +203,15 @@ def addIntIdSubscriber(ob, event):
Registers the object added in all unique id utilities and fires
an event for the catalogs.
"""
utilities = tuple(getAllUtilitiesRegisteredFor(IIntIds))
if utilities: # assert that there are any utilites
key = IKeyReference(ob, None)
utilities, key = _utilities_and_key(ob)
if not utilities or key is None:
# Register only objects that adapt to key reference
if key is not None:
idmap = {}
for utility in utilities:
idmap[utility] = utility.register(key)
# Notify the catalogs that this object was added.
notify(IntIdAddedEvent(ob, event, idmap))
return
idmap = {}
for utility in utilities:
idmap[utility] = utility.register(key)
# Notify the catalogs that this object was added.
notify(IntIdAddedEvent(ob, event, idmap))

@adapter(IIntIdEvent)
def intIdEventNotify(event):
Expand Down
19 changes: 18 additions & 1 deletion src/zope/intid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
"""
from zope.interface import Interface, Attribute, implementer

class IntIdMissingError(KeyError):
"""
Raised when ``getId`` cannot find an intid.
"""

class ObjectMissingError(KeyError):
"""
Raised when ``getObject`` cannot find an object.
"""

class IntIdsCorruptedError(KeyError):
"""
Raised when internal corruption is detected in the utility.
Users should not need to catch this because this situation should
not happen.
"""

class IIntIdsQuery(Interface):
"Query for IDs and objects"
Expand Down Expand Up @@ -43,7 +60,7 @@ def register(ob):
def unregister(ob):
"""Remove the object from the indexes.
KeyError is raised if ob is not registered previously.
IntIdMissingError is raised if ob is not registered previously.
"""

class IIntIdsManage(Interface):
Expand Down
Loading

0 comments on commit 12d653e

Please sign in to comment.