Skip to content
This repository has been archived by the owner on Mar 27, 2022. It is now read-only.

Commit

Permalink
started cleaning up the way of dealing with __magic__ methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Cuthbertson committed Apr 24, 2009
1 parent 5c9639d commit 652e7bb
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 66 deletions.
3 changes: 2 additions & 1 deletion README.markdown
Expand Up @@ -229,7 +229,8 @@ added. If you want to control this yourself, use `wrapper.frozen()` and

If you want to get advanced, you can also override special methods on a mock:

>>> wrapper = mock_wrapper().with_special( __len__ = lambda x: 5 )
#TODO: this doesn't currently work
>>> wrapper = mock_wrapper().with_children( __len__ = lambda x: 5 )
>>> len(wrapper.mock)
5

Expand Down
25 changes: 25 additions & 0 deletions mocktest/lib/singletonclass.py
@@ -0,0 +1,25 @@
def ensure_singleton_class(self):
try:
object.__getattr__(self, '_singleton_class_original_bases')
except AttributeError:
self._singleton_class_original_bases = type(self).__bases__
# make a new class that inherits from my current class, with the same name
new_class = type(type(self).__name__, (type(self),), {})
object.__setattr__(self, '__class__', new_class)

def revert_singleton_class(self):
try:
object.__getattr__(self, '_singleton_class_original_bases')
except:
return
type(self).__bases__ = self._singleton_class_original_bases


#handy mixin class
class SingletonClass(object):
def _ensure_singleton_class(self):
ensure_singleton_class(self)

def _revert_singleton_class(self):
revert_singleton_class(self)

1 change: 1 addition & 0 deletions mocktest/mockanchor.py
Expand Up @@ -146,6 +146,7 @@ def _reset(self):
def __getattr__(self, attr):
return self._make_mock_if_required(attr)

#TODO: handle __*****__ method names specially
def __setattr__(self, attr, val):
child = self._make_mock_if_required(attr)
child.return_value = val
Expand Down
7 changes: 2 additions & 5 deletions mocktest/mockwrapper.py
Expand Up @@ -115,12 +115,9 @@ def reset(self):
self.mock._mock_reset()

def child(self, val):
return mock_wrapper(getattr(self.mock, val))
return mock_wrapper(self.mock._mock_get_child(val))
method = child

def with_special(self, **kwargs):
self.mock._mock_set_special(**kwargs)
return self

# convenience methods for dsl-like chaining
def returning(self, val):
self.return_value = val
Expand Down
73 changes: 30 additions & 43 deletions mocktest/silentmock.py
Expand Up @@ -5,6 +5,7 @@
"""

from lib.realsetter import RealSetter
from lib.singletonclass import SingletonClass
from callrecord import CallRecord
from mockerror import MockError

Expand All @@ -19,12 +20,8 @@ def raw_mock(name = None, **kwargs):
kwargs['name'] = name
return SilentMock(**kwargs)

class SilentMock(RealSetter):
class SilentMock(RealSetter, SingletonClass):
def __init__(self, **kwargs):
# make self the only instance of a brand new class,
# allowing for special method assignment
self.__class__ = type(self.__class__.__name__, (self.__class__,), {})

self._real_set(_mock_dict = {
'action': None,
'return_value':DEFAULT,
Expand Down Expand Up @@ -70,26 +67,6 @@ def _mock_assert_can_set(self, attr, val):
self._mock_get('name'),
"a return value" if result_set == 'return_value' else 'an action'))

def _mock_set_special(self, **kwargs):
"""set special methods"""
for k,v in kwargs.items():
print "k = %s" % k
if not (k.startswith('__') and k.endswith('__')):
raise ValueError("%s is not a magic method" % (k,))

allowable = True
try:
allowable = getattr(self.__class__, k) == getattr(object, k)
except AttributeError:
pass
if not allowable:
# we overrode object's method - for a good reason
raise AttributeError("cannot override %s special method '%s'" % (self.__class__.__name__, k))

# we need to override the whole class' method
# in order for special methods to be used
setattr(self.__class__, k, v)

def _mock_get(self, attr, **kwargs):
if 'default' in kwargs:
return self._mock_dict.get(attr, kwargs['default'])
Expand Down Expand Up @@ -149,26 +126,19 @@ def _mock_fail_if_no_child_allowed(self, name):
if not self._mock_get('_modifiable_children'):
raise AttributeError, "object (%s) has no attribute '%s'" % (self, name,)

def _mock_special_method(self, name):
special_method = name.startswith('__') and name.endswith('__')
if special_method:
self._ensure_singleton_class()
return special_method

def __setattr__(self, attr, val):
if attr.startswith('__') and attr.endswith('__'):
return object.__setattr__(self, attr, val)
else:
self._mock_fail_if_no_child_allowed(attr)
self._mock_get('_children')[attr] = val
self._mock_fail_if_no_child_allowed(attr)
if self._mock_special_method(attr):
return object.__setattr__(type(self), attr, val)
self._mock_get('_children')[attr] = val

def __getattribute__(self, name):
if name.startswith('__') and name.endswith('__'):
# Attempt to get special methods directly, without exception
# handling
return object.__getattribute__(self, name)
elif name.startswith('_'):
try:
# Attempt to get the attribute, if that fails
# treat it as a child
return object.__getattribute__(self, name)
except AttributeError:
pass

def _mock_get_child(self, name):
def _new():
self._mock_get('_children')[name] = raw_mock(name=name)
return self._mock_get('_children')[name]
Expand All @@ -181,7 +151,24 @@ def _new():
child = self._mock_get('_children')[name]
if child is DEFAULT:
child = _new()
if self._mock_special_method(name):
setattr(type(self), name, child)
return child

def __getattribute__(self, name):
if name.startswith('__') and name.endswith('__'):
# Attempt to get special methods directly, without exception
# handling
return object.__getattribute__(self, name)
elif name.startswith('_'):
try:
# Attempt to get the attribute, if that fails
# treat it as a child
return object.__getattribute__(self, name)
except AttributeError:
pass
return self._mock_get_child(name)


def __str__(self):
return str(self._mock_get('name'))
28 changes: 11 additions & 17 deletions test/mock_test.py
Expand Up @@ -169,28 +169,22 @@ def return_foo():

def test_should_allow_setting_of_magic_methods(self):
clean_wrapper = mock_wrapper().named('clean')
modified_wrapper = mock_wrapper().named('modified').with_special(
__repr__ = mock_wrapper().returning('my repr!').mock,
__len__ = lambda x: 5)
modified_wrapper = mock_wrapper().named('modified')

modified_wrapper.method('__repr__').returning('my repr!')
modified_wrapper.method('__str__').returning('my str!')
modified_wrapper.method('__len__').returning(5)

self.assertNotEqual(clean_wrapper.mock.__class__, modified_wrapper.mock.__class__)
self.assertNotEqual(type(clean_wrapper.mock), type(modified_wrapper.mock))
self.assertEqual(type(clean_wrapper.mock).__name__, type(modified_wrapper.mock).__name__)

val = repr(modified_wrapper.mock)
self.assertTrue(modified_wrapper.child('__repr__').called.once())
self.assertEqual(val, 'my repr!')
self.assertTrue(modified_wrapper.child('__repr__').called.once())

# can't override builtin mock magic methods
self.assertRaises(
AttributeError, lambda: modified_wrapper.with_special(__str__ = lambda x: 'foop'),
message="cannot override SilentMock special method '__str__'")

# can't assign non-magic ones
self.assertRaises(ValueError, lambda: modified_wrapper.with_special(_len = lambda x: 5))

self.assertEqual(len(modified_wrapper.mock), 5)
self.assertRaises(AttributeError, lambda: clean_wrapper.mock.__len__)
self.assertRaises(TypeError, lambda: len(clean_wrapper.mock))
self.assertEqual(str(clean_wrapper.mock), 'clean')
str_val = str(modified_wrapper.mock)
self.assertEqual(str_val, 'my str!')
self.assertTrue(modified_wrapper.child('__str__').called.once())

def test_should_show_where_calls_were_made(self):
wrapper = mock_wrapper()
Expand Down

0 comments on commit 652e7bb

Please sign in to comment.