Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternate operator #15

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions should_dsl/doctests/alternate_operator_when_ror_is_overriden.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
>>> from should_dsl import should, should_not

Objects that have an __or__ method will not work properly with standard should-dsl form, that uses __ror__ operator. For these cases, should_dsl provides an alternative form using the div operator:

>>> class Spam:
... def __or__(self, outro):
... return True

>>> spam = Spam()
>>> spam |should| be(spam)
Traceback (most recent call last):
...
NameError: name 'be' is not defined

>>> spam /should/ be(spam)


If the object having __or__ is the expected object, everything works normally.

>>> "spam" |should_not| be(spam)


The alternative form can only be used if the actual object really needs it:

>>> class Eggs: pass

>>> eggs = Eggs()
>>> eggs /should/ be(eggs)
Traceback (most recent call last):
...
TypeError: /should/ is supported only if the actual object overrides __or__, use |should| instead

>>> "a" /should/ equal_to("a")
Traceback (most recent call last):
...
TypeError: /should/ is supported only if the actual object overrides __or__, use |should| instead


Built-in objects that have an __or__ method (e.g. integer numbers, True and False) but works fine with standard |should| will not accept the div form:

>>> 1 /should/ be(1)
Traceback (most recent call last):
...
TypeError: /should/ is supported only if the actual object overrides __or__, use |should| instead


>>> True /should/ be(True)
Traceback (most recent call last):
...
TypeError: /should/ is supported only if the actual object overrides __or__, use |should| instead

20 changes: 18 additions & 2 deletions should_dsl/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,24 @@ def _evaluate(self, value):
return value

def __ror__(self, lvalue):
return self._left_operator_action(lvalue)

def __rdiv__(self, lvalue):
self._ensure_actual_object_needs_div_operator(lvalue)
return self._left_operator_action(lvalue)

def _left_operator_action(self, lvalue):
self._lvalue = lvalue
self._create_function_matchers()
return self

def __or__(self, rvalue):
return self._right_operator_action(rvalue)

def __div__(self, rvalue):
return self._right_operator_action(rvalue)

def _right_operator_action(self, rvalue):
self._destroy_function_matchers()
self._rvalue = rvalue
return self._check_expectation()
Expand All @@ -35,9 +48,12 @@ def _check_expectation(self):
self._rvalue.message_for_failed_should_not() or \
self._rvalue.message_for_failed_should())

def _ensure_actual_object_needs_div_operator(self, actual):
if not (hasattr(actual, '__or__') and 'instance' in str(type(actual))):
raise TypeError("/should/ is supported only if the actual object overrides __or__, use |should| instead")

def _destroy_function_matchers(self):
self._outer_frame = sys._getframe(2).f_globals
self._outer_frame = sys._getframe(3).f_globals
self._remove_matchers_from_namespace()
self._put_original_identifiers_back()

Expand All @@ -64,7 +80,7 @@ def _put_original_identifiers_back(self):


def _create_function_matchers(self):
self._outer_frame = sys._getframe(2).f_globals
self._outer_frame = sys._getframe(3).f_globals
self._save_clashed_identifiers()
self._put_matchers_on_namespace()

Expand Down