Skip to content

Commit

Permalink
Allow overwriting of monkey-patched properties by sure. Closes gabrie…
Browse files Browse the repository at this point in the history
…lfalcao#19, gabrielfalcao#74

This allows overwriting of the monkey-patched properties by sure in
cpython:

```python

from sure import AssertionBuilder

class Foo(object):
    pass

instance = Foo()
instance.do = "anything"
instance.do.should.be.equal("anything")

instance2 = Foo()
instance2.do.__class__.should.be.equal(AssertionBuilder)
```

This test would pass.
  • Loading branch information
timofurrer committed Jun 21, 2016
1 parent 4b99391 commit b3da667
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- anything object which is accessible with `sure.anything`

### Fixed
- Allow overwriting of monkey-patched properties by sure. Refs #19

## [v1.3.0]
### Added
- Python 3.3, 3.4 and 3.5 support
Expand Down
29 changes: 21 additions & 8 deletions sure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import inspect
import traceback

from functools import wraps
from functools import wraps, partial
from datetime import datetime

from six import string_types, text_type, PY3, get_function_code
Expand Down Expand Up @@ -937,25 +937,28 @@ def match(self, regex, *args):


if is_cpython and allows_new_syntax:

def make_safe_property(method, name, should_be_property=True):
if not should_be_property:
return method(None)

def deleter(self, *args, **kw):
pass
def deleter(method, self, *args, **kw):
del overwritten_object_handlers[(id(self), method.__name__)]

def setter(self, other):
pass
def setter(method, self, other):
overwritten_object_handlers[(id(self), method.__name__)] = other

return builtins.property(
fget=method,
fset=setter,
fdel=deleter,
fset=partial(setter, method),
fdel=partial(deleter, method),
)

def positive_assertion(name, prop=True):
def method(self):
overwritten_object_handler = overwritten_object_handlers.get((id(self), name), None)
if overwritten_object_handler:
return overwritten_object_handler

builder = AssertionBuilder(name, negative=False)
instance = builder(self)
callable_args = getattr(self, '_callable_args', ())
Expand All @@ -971,6 +974,10 @@ def method(self):

def negative_assertion(name, prop=True):
def method(self):
overwritten_object_handler = overwritten_object_handlers.get((id(self), name), None)
if overwritten_object_handler:
return overwritten_object_handler

builder = AssertionBuilder(name, negative=True)
instance = builder(self)
callable_args = getattr(self, '_callable_args', ())
Expand All @@ -985,6 +992,12 @@ def method(self):
return make_safe_property(method, name, prop)

object_handler = patchable_builtin(object)
# We have to keep track of all objects which
# should overwrite a ``POSITIVES`` or ``NEGATIVES``
# property. If we wouldn't do that in the
# make_safe_property.setter method we would loose
# the newly assigned object reference.
overwritten_object_handlers = {}

# None does not have a tp_dict associated to its PyObject, so this
# is the only way we could make it work like we expected.
Expand Down
40 changes: 40 additions & 0 deletions tests/issues/test_issue_19.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-

"""
Test fix of bug described in GitHub Issue #19.
"""

from sure import expect, AssertionBuilder
from sure.magic import is_cpython


def test_issue_19():
"Allow monkey-patching of methods already implemented by sure."

class Foo(object):
pass

@property
def should(self):
return 42

instance = Foo()
instance.do = "anything"
instance.doesnt = "foo"

expect(instance.do).should.be.equal("anything")
expect(instance.doesnt).should.be.equal("foo")

del instance.do

if is_cpython:
instance.do.shouldnt.be.equal("anything")
else:
expect(instance).shouldnt.have.property("do")

if is_cpython:
instance2 = Foo()
instance2.do.shouldnt.be.equal("anything")
instance.does.__class__.should.be.equal(AssertionBuilder)

expect(instance.should).should.be.equal(42)

0 comments on commit b3da667

Please sign in to comment.