Skip to content

Commit

Permalink
Merge pull request #14 from life4/ensure
Browse files Browse the repository at this point in the history
@deal.ensure
  • Loading branch information
orsinium authored Oct 2, 2019
2 parents 774f9dd + 8987b86 commit 60a0ac8
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
language: python
dist: xenial

# do not run Travis for PR's twice (as for push and as for PR)
branches:
only:
- master

before_install:
# show a little bit more information about environment
- sudo apt-get install -y tree
Expand Down
5 changes: 3 additions & 2 deletions deal/aliases.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from functools import partial

from .core import Pre, Post, Invariant, Raises, Offline, Silent
from .core import Pre, Post, Invariant, Raises, Offline, Silent, Ensure


__all__ = [
Expand All @@ -14,7 +14,8 @@


require = pre = Pre
ensure = post = Post
post = Post
ensure = Ensure
inv = invariant = Invariant
raises = Raises

Expand Down
16 changes: 16 additions & 0 deletions deal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,19 @@ def patched_function(self, *args, **kwargs):
finally:
sys.stdout = true_stdout
sys.stderr = true_stderr


class Ensure(_Base):
"""
Check both arguments and result (validator) after function processing.
Validate arguments and output result.
"""
exception = exceptions.PostContractError

def patched_function(self, *args, **kwargs):
"""
Step 3. Wrapped function calling.
"""
result = self.function(*args, **kwargs)
self.validate(*args, result=result, **kwargs)
return result
41 changes: 41 additions & 0 deletions docs/decorators/ensure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ensure

Ensure is a [postcondition](./post) that aceepts not only result, but also function arguments. Must be true after function executed. Raises `PostContractError` otherwise.

```python
@deal.ensure(lambda x, result: x != result)
def double(x):
return x * 2

double(2)
# 4

double(0)
# PostContractError:
```

## Motivation

Perfect for complex task that easy to check. For example:

```python
from typing import List

# element at this position matches item
@deal.ensure(
lambda items, item, result: items[result] == item,
message='invalid match',
)
# element at this position is the first match
@deal.ensure(
lambda items, item, result: not any(el == item for el in items[:result]),
message='not the first match',
)
def index_of(items: List[int], item: int) -> int:
for index, element in enumerate(items):
if element == item:
return index
raise LookupError
```

It allows you to simplify testing, easier check hypothesis, tell more about the function behavior.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
decorators/pre
decorators/post
decorators/ensure
decorators/inv
.. toctree::
Expand Down
22 changes: 22 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,5 +460,27 @@ def test_main(self):
func(-2)


class EnsureTest(unittest.TestCase):
def test_main(self):
@deal.ensure(lambda a, b, result: a > 0 and b > 0 and result != 'same number')
def func(a, b):
if a == b:
return 'same number'
else:
return 'different numbers'

with self.subTest(text='good'):
self.assertEqual(func(1, 2), 'different numbers')
with self.subTest(text='argument error on a'):
with self.assertRaises(deal.PostContractError):
func(0, 1)
with self.subTest(text='argument error on b'):
with self.assertRaises(deal.PostContractError):
func(1, 0)
with self.subTest(text='result error'):
with self.assertRaises(deal.PostContractError):
func(1, 1)


if __name__ == '__main__':
pytest.main(['tests.py'])

0 comments on commit 60a0ac8

Please sign in to comment.