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

  • ZeroDivisionErrorb == 0

Contracts
  • b is not zero

  • b != 0

  • res != .13

  • b != result

Examples
  • example(6, 2) == 3