Examples¶
choice¶
import random
from typing import List
import deal
# the list cannot be empty
@deal.pre(lambda items: bool(items))
# result is an element withit the given list
@deal.ensure(lambda items, result: result in items)
@deal.has('random')
def choice(items: List[str]) -> str:
"""Get a random element from the given list.
"""
return random.choice(items)
test_choice = deal.cases(choice)
concat¶
import deal
@deal.ensure(lambda _: _.result.startswith(_.left))
@deal.ensure(lambda _: _.result.endswith(_.right))
@deal.ensure(lambda _: len(_.result) == len(_.left) + len(_.right))
@deal.has()
def concat(left: str, right: str) -> str:
"""Concatenate 2 given strings.
https://deal.readthedocs.io/basic/motivation.html
"""
return left + right
test_concat = deal.cases(concat)
count¶
from typing import List
import deal
# In short signature, `_` is a `dict` with access by attributes.
# Hence it has all dict attributes. So, if argument we need conflicts
# with a dict attribute, use getitem instead of getattr.
# In the example below, we use `_['items']` instead of `_.items`.
@deal.post(lambda result: result >= 0)
# if count is not zero, `item` appears in `items` at least once.
@deal.ensure(lambda _: _.result == 0 or _['item'] in _['items'])
# if count is zero, `item` is not in `items`
@deal.ensure(lambda _: _.result != 0 or _['item'] not in _['items'])
@deal.has()
def count(items: List[str], item: str) -> int:
"""How many times `item` appears in `items`
"""
return items.count(item)
test_count = deal.cases(count)
div¶
import deal
@deal.raises(ZeroDivisionError)
@deal.reason(ZeroDivisionError, lambda _: _.right == 0)
@deal.has()
def div1(left: float, right: float) -> float:
"""
This implementation allows zero to be passed
but raises ZeroDivisionError in that case.
"""
return left / right
@deal.pre(lambda _: _.right != 0)
@deal.has()
def div2(left: float, right: float) -> float:
"""
This implementation doesn't allow zero to be passed in a function.
If it is accidentally passed, PreConditionError will be raised
and the funcrion won't be executed.
"""
return left / right
test_div1 = deal.cases(div1)
test_div2 = deal.cases(div2)
fuzzing_atheris¶
"""
Get help for libFuzzer:
python3 examples/fuzzing_atheris.py -help=1
Run 1000 test cases:
python3 examples/fuzzing_atheris.py -runs=1000
"""
import codecs
import sys
import atheris
import deal
def encode(text: str) -> str:
return codecs.encode(text, encoding='rot13')
@deal.ensure(lambda text, result: encode(result) == text)
def decode(text: str) -> str:
assert text != 'bad'
return codecs.encode(text, encoding='rot13')
def fuzz():
test = deal.cases(decode)
atheris.Setup(sys.argv, test)
atheris.Fuzz()
if __name__ == '__main__':
fuzz()
fuzzing_pythonfuzz¶
import codecs
from pythonfuzz.main import PythonFuzz
import deal
def encode(text: str) -> str:
return codecs.encode(text, encoding='rot13')
@deal.ensure(lambda text, result: encode(result) == text)
def decode(text: str) -> str:
assert text != 'bad'
return codecs.encode(text, encoding='rot13')
def fuzz():
test = deal.cases(decode)
PythonFuzz(test)()
if __name__ == '__main__':
fuzz()
using_hypothesis¶
from typing import List
import hypothesis
import deal
@deal.pre(lambda items: len(items) > 0)
@deal.has()
def my_min(items: List[int]) -> int:
return min(items)
@hypothesis.example([1, 2, 3])
@deal.cases(
func=my_min,
settings=hypothesis.settings(
verbosity=hypothesis.Verbosity.normal,
),
)
def test_min(case):
case()
if __name__ == '__main__':
test_min()
index_of¶
from typing import List
import deal
# if you have more than 2-3 contracts,
# consider moving them from decorators into separate variable
# like this:
contract_for_index_of = deal.chain(
# result is an index of items
deal.post(lambda result: result >= 0),
deal.ensure(lambda items, item, result: result < len(items)),
# 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',
),
# LookupError will be raised if no elements found
deal.raises(LookupError),
deal.reason(LookupError, lambda items, item: item not in items),
# no side-effects
deal.has(),
)
@contract_for_index_of
def index_of(items: List[int], item: int) -> int:
for index, el in enumerate(items):
if el == item:
return index
raise LookupError
test_index_of = deal.cases(index_of)
min¶
from typing import List
import deal
@deal.pre(lambda items: len(items) > 0)
@deal.has()
def my_min(items: List[int]) -> int:
return min(items)
@deal.has('stdout')
def example():
# good
print(my_min([3, 1, 4]))
# bad
print(my_min([]))
test_min = deal.cases(my_min)
Linter output:
$ python3 -m deal lint examples/min.py
examples/min.py
21:4 DEL011 pre contract error ([])
my_min([])
^
format¶
import re
import deal
def contract(template: str, *args):
rex = re.compile(r'\{\:([a-z])\}')
types = {'s': str, 'd': float}
matches = rex.findall(template)
if len(matches) != len(args):
return f'expected {len(matches)} argument(s) but {len(args)} found'
for match, arg in zip(matches, args):
t = types[match[0]]
if not isinstance(arg, t):
return f'expected {t.__name__}, {type(arg).__name__} given'
return True
@deal.pre(contract)
def format(template: str, *args) -> str:
return template.format(*args)
@deal.has('io')
def example():
# good
print(format('{:s}', 'hello'))
# bad
print(format('{:s}')) # not enough args
print(format('{:s}', 'a', 'b')) # too many args
print(format('{:d}', 'a')) # bad type
if __name__ == '__main__':
print(format('{:s} {:s}', 'hello', 'world'))
Linter output:
$ python3 -m deal lint examples/format.py
examples/format.py
32:10 DEL011 expected 1 argument(s) but 0 found ('{:s}')
print(format('{:s}')) # not enough args
^
33:10 DEL011 expected 1 argument(s) but 2 found ('{:s}', 'a', 'b')
print(format('{:s}', 'a', 'b')) # too many args
^
34:10 DEL011 expected float, str given ('{:d}', 'a')
print(format('{:d}', 'a')) # bad type
^
sphinx¶
Source code:
import deal
@deal.reason(ZeroDivisionError, lambda a, b: b == 0)
@deal.reason(ValueError, lambda a, b: a == b, message='a is equal to b')
@deal.raises(ValueError, IndexError, ZeroDivisionError)
@deal.pre(lambda a, b: b != 0)
@deal.pre(lambda a, b: b != 0, message='b is not zero')
@deal.ensure(lambda a, b, result: b != result)
@deal.post(lambda res: res != .13)
@deal.has('database', 'network')
@deal.example(lambda: example(6, 2) == 3)
def example(a: int, b: int) -> float:
"""Example function.
:return: The description for return value.
"""
return a / b
Sphinx config (docs/conf.py
):
import deal
extensions = ['sphinx.ext.autodoc']
def setup(app):
deal.autodoc(app)
Including into a documentation page (docs/index.rst
):
.. autofunction:: examples.sphinx.example
Generated output:
-
examples.sphinx.
example
(a: int, b: int) → float[source]¶ Example function.
- Returns
The description for return value.
- Side-effects
network
database
- Raises
IndexError –
ValueError – a is equal to b
ZeroDivisionError –
b == 0
- Contracts
b is not zero
b != 0
res != .13
b != result
- Examples
example(6, 2) == 3