diff --git a/should_dsl/doctests/alternate_operator_when_ror_is_overriden.txt b/should_dsl/doctests/alternate_operator_when_ror_is_overriden.txt new file mode 100644 index 0000000..5996937 --- /dev/null +++ b/should_dsl/doctests/alternate_operator_when_ror_is_overriden.txt @@ -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 + diff --git a/should_dsl/dsl.py b/should_dsl/dsl.py index aa4ad2c..3f10fc5 100644 --- a/should_dsl/dsl.py +++ b/should_dsl/dsl.py @@ -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() @@ -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() @@ -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()